diff --git a/src/lib.rs b/src/lib.rs index e03495448e..7169d27d85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,7 +226,7 @@ #![cfg_attr(doc_cfg, feature(doc_cfg))] #![cfg_attr( __INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS, - feature(layout_for_ptr, strict_provenance) + feature(const_size_of_val_raw, layout_for_ptr, strict_provenance) )] // This is a hack to allow zerocopy-derive derives to work in this crate. They @@ -335,19 +335,22 @@ const _: () = { /// [the reference]: https://doc.rust-lang.org/reference/type-layout.html #[doc(hidden)] #[allow(missing_debug_implementations, missing_copy_implementations)] -#[cfg_attr(test, derive(Copy, Clone, Debug, PartialEq, Eq))] +#[cfg_attr(any(test, kani), derive(Copy, Clone, Debug, PartialEq, Eq))] +#[cfg_attr(kani, derive(kani::Arbitrary))] pub struct DstLayout { _align: NonZeroUsize, _size_info: SizeInfo, } -#[cfg_attr(test, derive(Copy, Clone, Debug, PartialEq, Eq))] +#[cfg_attr(any(test, kani), derive(Copy, Clone, Debug, PartialEq, Eq))] +#[cfg_attr(kani, derive(kani::Arbitrary))] enum SizeInfo { Sized { _size: usize }, SliceDst(TrailingSliceLayout), } -#[cfg_attr(test, derive(Copy, Clone, Debug, PartialEq, Eq))] +#[cfg_attr(any(test, kani), derive(Copy, Clone, Debug, PartialEq, Eq))] +#[cfg_attr(kani, derive(kani::Arbitrary))] struct TrailingSliceLayout { // The offset of the first byte of the trailing slice field. Note that this // is NOT the same as the minimum size of the type. For example, consider @@ -439,6 +442,61 @@ impl DstLayout { } } + /// Constructs a `DstLayout` which describes a dynamically-sized, `repr(C)` + /// type with the given properties. + /// + /// # Safety + /// + /// Unsafe code may assume that the returned `DstLayout` is the correct + /// layout for the described type so long as: + /// - The type is `repr(C)` + /// - The type has the alignment `align` + /// - The type has at least one field + /// - The byte offset of the first byte of the trailing field is equal to + /// `trailing_field_offset` + /// - The trailing field's layout is correctly described by + /// `trailing_field_layout` + #[doc(hidden)] + #[inline] + pub const fn for_repr_c_dst( + align: NonZeroUsize, + trailing_field_offset: usize, + trailing_field_layout: DstLayout, + ) -> Option { + Some(DstLayout { + // SAFETY: The caller has promised that this is the correct + // alignment. + _align: align, + _size_info: match trailing_field_layout._size_info { + SizeInfo::Sized { _size } => { + let without_padding = match trailing_field_offset.checked_add(_size) { + Some(without_padding) => without_padding, + None => return None, + }; + + let padding = util::core_layout::_padding_needed_for(without_padding, align); + + let _size = match without_padding.checked_add(padding) { + Some(_size) => _size, + None => return None, + }; + + // SAFETY: TODO + SizeInfo::Sized { _size } + } + SizeInfo::SliceDst(TrailingSliceLayout { _offset, _elem_size }) => { + let offset = match trailing_field_offset.checked_add(_offset) { + Some(offset) => offset, + None => return None, + }; + + // SAFETY: TODO + SizeInfo::SliceDst(TrailingSliceLayout { _offset: offset, _elem_size }) + } + }, + }) + } + /// Validates that a cast is sound from a layout perspective. /// /// Validates that the size and alignment requirements of a type with the @@ -3908,6 +3966,82 @@ mod tests { } } + mod for_repr_c_dst { + use super::*; + + #[test] + fn test_sized() { + let align = NonZeroUsize::new(8).unwrap(); + let trailing_field_offset = 7; + let trailing_field_layout = DstLayout::for_type::<[u8; 3]>(); + + let composite = + DstLayout::for_repr_c_dst(align, trailing_field_offset, trailing_field_layout) + .unwrap(); + + // The alignment is that of the composite type, not that of its + // trailing field. + assert_eq!(composite._align, align); + + if let SizeInfo::Sized { _size } = composite._size_info { + // The size of the trailing field is 3. + assert!(matches!(trailing_field_layout._size_info, SizeInfo::Sized { _size: 3 })); + + // The size of the composite type is 16. + assert_eq!(_size, 16); + + // This is true, because the size of the composite type without + // padding is 10. + let without_padding = trailing_field_offset + 3; + assert_eq!(without_padding, 10); + + // And once padding is added, the size is rounded up to 16 (the + // next multiple of `align`). + let padding = util::core_layout::_padding_needed_for(without_padding, align); + assert_eq!(padding, 6); + + assert_eq!(_size, without_padding + padding); + } else { + panic!("`SizeInfo::Sized` expected"); + } + } + + #[test] + fn test_unsized() { + let align = NonZeroUsize::new(8).unwrap(); + let trailing_field_offset = 7; + let trailing_field_layout = DstLayout { + _align: NonZeroUsize::new(1).unwrap(), + _size_info: SizeInfo::SliceDst(TrailingSliceLayout { + _offset: 3, + _elem_size: usize::MAX, + }), + }; + + let composite = + DstLayout::for_repr_c_dst(align, trailing_field_offset, trailing_field_layout) + .unwrap(); + + // The alignment is that of the composite type, not that of its + // trailing field. + assert_eq!(composite._align, align); + + if let SizeInfo::SliceDst(TrailingSliceLayout { _offset, _elem_size }) = + composite._size_info + { + // The offset of the trailing slice in the composite type is the + // sum of the offset of the trailing field, plus the offset of + // the slice within that field. + assert_eq!(_offset, 10); + + // The elem size is unaffected. + assert_eq!(_elem_size, usize::MAX); + } else { + panic!("`SizeInfo::SliceDst` expected"); + } + } + } + // This test takes a long time when running under Miri, so we skip it in // that case. This is acceptable because this is a logic test that doesn't // attempt to expose UB. @@ -5600,7 +5734,7 @@ mod tests { assert_impls!(Wrapping: KnownLayout, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); assert_impls!(Unalign: KnownLayout, FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(Unalign: KnownLayout, Unaligned, !FromZeroes, !FromBytes, !AsBytes); + assert_impls!(Unalign: Unaligned, !FromZeroes, !FromBytes, !AsBytes); assert_impls!([u8]: KnownLayout, FromZeroes, FromBytes, AsBytes, Unaligned); assert_impls!([NotZerocopy]: !KnownLayout, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); @@ -5677,3 +5811,91 @@ mod tests { } } } + +#[cfg(kani)] +mod proofs { + use super::*; + + #[kani::proof] + fn prove_for_repr_c_dst() { + let align: NonZeroUsize = kani::any(); + let trailing_field_offset: usize = kani::any(); + let trailing_field_layout: DstLayout = kani::any(); + + kani::assume(align.is_power_of_two()); + + let maybe_dst_layout = + DstLayout::for_repr_c_dst(align, trailing_field_offset, trailing_field_layout); + + if let Some(dst_layout) = maybe_dst_layout { + // The alignment of the `DstLayout` produced by `for_repr_c_dst` depends + // only upon the `align` parameter provided to `for_repr_c_dst`. + assert_eq!(dst_layout._align, align); + match dst_layout._size_info { + SizeInfo::Sized { _size: total_size } => { + // If the resulting `DstLayout` is sized, so too must be + // `trailing_field_layout`. + if let SizeInfo::Sized { _size: trailing_size } = + trailing_field_layout._size_info + { + // The total size should equal the offset of the + // trailing field, plus the size of the trailing field, + // plus any trailing padded needed for alignment. + assert_eq!(total_size, { + let without_padding = trailing_field_offset + trailing_size; + let padding = + util::core_layout::_padding_needed_for(without_padding, align); + without_padding + padding + }); + } else { + panic!("DstLayout._size_info should describe a SliceDst type"); + } + } + SizeInfo::SliceDst(TrailingSliceLayout { + _offset: total_offset, + _elem_size: outer_elem_size, + }) => { + // If the resulting `DstLayout` is unsized, so too must be + // `trailing_field_layout`. + if let SizeInfo::SliceDst(TrailingSliceLayout { + _offset: inner_trailing_offset, + _elem_size: trailing_elem_size, + }) = trailing_field_layout._size_info + { + // The elem_size of the trailing slice component is unchanged. + assert_eq!(outer_elem_size, trailing_elem_size); + // The offset of the trailing slice component within the + // trailing field should be added to the offset of the + // trailing. + assert_eq!(total_offset, trailing_field_offset + inner_trailing_offset); + } else { + panic!("DstLayout._size_info should describe a Sized type"); + } + } + } + } else { + // If `None` is produced, then, either: + match trailing_field_layout._size_info { + SizeInfo::Sized { _size: trailing_size } => { + let without_padding = trailing_field_offset.checked_add(trailing_size); + + let total_size = without_padding.and_then(|without_padding| { + let padding = + util::core_layout::_padding_needed_for(without_padding, align); + without_padding.checked_add(padding) + }); + + // ...some aspect of computing size failed due to overflow. + assert!(without_padding.is_none() || total_size.is_none()); + } + SizeInfo::SliceDst(TrailingSliceLayout { _offset, _elem_size }) => { + // ...computing the trailing offset failed due to overflow. + assert!(trailing_field_offset.checked_add(_offset).is_none()); + } + } + } + } + + #[kani::proof] + fn prove_for_repr_c_dst_rejects_overflow() {} +} diff --git a/src/macro_util.rs b/src/macro_util.rs index 24fec4f015..fadfdeaf01 100644 --- a/src/macro_util.rs +++ b/src/macro_util.rs @@ -66,7 +66,7 @@ impl MaxAlignsOf { } } -const _64K: usize = 1 << 16; +pub const _64K: usize = 1 << 16; // TODO(#29), TODO(https://github.com/rust-lang/rust/issues/69835): Remove this // `cfg` when `size_of_val_raw` is stabilized. @@ -146,10 +146,16 @@ macro_rules! trailing_field_offset { } }; - assert!(min_size <= _64K); + if min_size > $crate::macro_util::_64K { + panic!(concat!( + "cannot compute layout of `", + stringify!($ty), + "`; type is larger than zerocopy supports" + )) + } #[allow(clippy::as_conversions)] - let ptr = ALIGNED_64K_ALLOCATION.as_ptr() as *const $ty; + let ptr = $crate::macro_util::ALIGNED_64K_ALLOCATION.as_ptr() as *const $ty; // SAFETY: // - Thanks to the preceding `assert!`, we know that the value with zero @@ -173,7 +179,7 @@ macro_rules! trailing_field_offset { // The size of a value is always a multiple of its alignment. // // [2] https://github.com/rust-lang/reference/pull/1387 - let field = unsafe { + let field: *const _ = unsafe { $crate::macro_util::core_reexport::ptr::addr_of!((*ptr).$trailing_field_name) }; // SAFETY: @@ -196,12 +202,10 @@ macro_rules! trailing_field_offset { // Guaranteed not to be lossy: `field` comes after `ptr`, so the offset // from `ptr` to `field` is guaranteed to be positive. assert!(offset >= 0); - Some( - #[allow(clippy::as_conversions)] - { - offset as usize - }, - ) + #[allow(clippy::as_conversions)] + { + offset as usize + } }}; } @@ -236,15 +240,58 @@ macro_rules! align_of { // [1]: https://doc.rust-lang.org/nomicon/other-reprs.html#reprc #[repr(C)] - struct OffsetOfTrailingIsAlignment { + struct OffsetOfTrailingIsAlignment { _byte: u8, - _trailing: $ty, + _trailing: T, } - trailing_field_offset!(OffsetOfTrailingIsAlignment, _trailing) + let trailing_field_offset = + $crate::trailing_field_offset!(OffsetOfTrailingIsAlignment<$ty>, _trailing); + + match $crate::macro_util::core_reexport::num::NonZeroUsize::new(trailing_field_offset) { + Some(trailing_field_offset) => trailing_field_offset, + None => unreachable!(), + } }}; } +/// Constructs the `DstLayout` of a given `$ty` that (optionally) has a +/// `$trailing_field_name`. If `$ty` is a struct and `$trailing_field_name` is +/// provided, this macro will compile successfully even if `$ty` is unsized +/// (otherwise it will not). If `$ty` is sized, then `$trailing_field_name` may +/// be omitted. +// TODO(#29), TODO(https://github.com/rust-lang/rust/issues/69835): Remove this +// `cfg` and the subsequent fallback when `size_of_val_raw` is stabilized. +#[cfg(__INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS)] +#[macro_export] +macro_rules! repr_c_dst_layout { + ($ty:ty) => { + $crate::DstLayout::for_type::<$ty>() + }; + ($ty:ty, $trailing_field_name:tt: $trailing_field_ty:ty) => {{ + let align = $crate::align_of!($ty); + let maybe_layout = $crate::DstLayout::for_repr_c_dst( + align, + $crate::trailing_field_offset!($ty, $trailing_field_name), + <$trailing_field_ty as $crate::KnownLayout>::LAYOUT, + ); + match maybe_layout { + Some(layout) => layout, + None => panic!(concat!("Could not compute layout of `", stringify!($ty), "`.")), + } + }}; +} +#[cfg(not(__INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS))] +#[macro_export] +macro_rules! repr_c_dst_layout { + ($ty:ty) => { + $crate::DstLayout::for_type::<$ty>() + }; + ($ty:ty, $_trailing_field_name:tt: $_trailing_field_ty:ty) => { + $crate::repr_c_dst_layout!($ty) + }; +} + /// Does the struct type `$t` have padding? /// /// `$ts` is the list of the type of every field in `$t`. `$t` must be a @@ -407,6 +454,18 @@ pub mod core_reexport { pub mod mem { pub use core::mem::*; + + // TODO(#29), TODO(https://github.com/rust-lang/rust/issues/69835): Remove this + // function when `size_of_val_raw` is stabilized. + #[cfg(__INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS)] + #[inline(always)] + pub const unsafe fn size_of_val_raw(val: *const T) -> usize + where + T: ?Sized, + { + // SAFETY: See `core::mem::size_of_val_raw` docs. + unsafe { core::mem::size_of_val_raw(val) } + } } } @@ -489,83 +548,83 @@ mod tests { (@offset $_t:ty ; $_trailing:ty) => { trailing_field_offset!(Test, 1) }; } - test!(#[repr(C)] #[repr(transparent)] #[repr(packed)](; u8) => Some(0)); - test!(#[repr(C)] #[repr(transparent)] #[repr(packed)](; [u8]) => Some(0)); - test!(#[repr(C)] #[repr(packed)] (u8; u8) => Some(1)); - test!(#[repr(C)] (; AU64) => Some(0)); - test!(#[repr(C)] (; [AU64]) => Some(0)); - test!(#[repr(C)] (u8; AU64) => Some(8)); - test!(#[repr(C)] (u8; [AU64]) => Some(8)); - test!(#[repr(C)] (; Nested) => Some(0)); - test!(#[repr(C)] (; Nested) => Some(0)); - test!(#[repr(C)] (u8; Nested) => Some(8)); - test!(#[repr(C)] (u8; Nested) => Some(8)); + test!(#[repr(C)] #[repr(transparent)] #[repr(packed)](; u8) => 0); + test!(#[repr(C)] #[repr(transparent)] #[repr(packed)](; [u8]) => 0); + test!(#[repr(C)] #[repr(packed)] (u8; u8) => 1); + test!(#[repr(C)] (; AU64) => 0); + test!(#[repr(C)] (; [AU64]) => 0); + test!(#[repr(C)] (u8; AU64) => 8); + test!(#[repr(C)] (u8; [AU64]) => 8); + test!(#[repr(C)] (; Nested) => 0); + test!(#[repr(C)] (; Nested) =>0); + test!(#[repr(C)] (u8; Nested) => 8); + test!(#[repr(C)] (u8; Nested) => 8); // Test that `packed(N)` limits the offset of the trailing field. - test!(#[repr(C, packed( 1))] (u8; elain::Align< 2>) => Some( 1)); - test!(#[repr(C, packed( 2))] (u8; elain::Align< 4>) => Some( 2)); - test!(#[repr(C, packed( 4))] (u8; elain::Align< 8>) => Some( 4)); - test!(#[repr(C, packed( 8))] (u8; elain::Align< 16>) => Some( 8)); - test!(#[repr(C, packed( 16))] (u8; elain::Align< 32>) => Some( 16)); - test!(#[repr(C, packed( 32))] (u8; elain::Align< 64>) => Some( 32)); - test!(#[repr(C, packed( 64))] (u8; elain::Align< 128>) => Some( 64)); - test!(#[repr(C, packed( 128))] (u8; elain::Align< 256>) => Some( 128)); - test!(#[repr(C, packed( 256))] (u8; elain::Align< 512>) => Some( 256)); - test!(#[repr(C, packed( 512))] (u8; elain::Align< 1024>) => Some( 512)); - test!(#[repr(C, packed( 1024))] (u8; elain::Align< 2048>) => Some( 1024)); - test!(#[repr(C, packed( 2048))] (u8; elain::Align< 4096>) => Some( 2048)); - test!(#[repr(C, packed( 4096))] (u8; elain::Align< 8192>) => Some( 4096)); - test!(#[repr(C, packed( 8192))] (u8; elain::Align< 16384>) => Some( 8192)); - test!(#[repr(C, packed( 16384))] (u8; elain::Align< 32768>) => Some( 16384)); - test!(#[repr(C, packed( 32768))] (u8; elain::Align< 65536>) => Some( 32768)); - test!(#[repr(C, packed( 65536))] (u8; elain::Align< 131072>) => Some( 65536)); + test!(#[repr(C, packed( 1))] (u8; elain::Align< 2>) => 1); + test!(#[repr(C, packed( 2))] (u8; elain::Align< 4>) => 2); + test!(#[repr(C, packed( 4))] (u8; elain::Align< 8>) => 4); + test!(#[repr(C, packed( 8))] (u8; elain::Align< 16>) => 8); + test!(#[repr(C, packed( 16))] (u8; elain::Align< 32>) => 16); + test!(#[repr(C, packed( 32))] (u8; elain::Align< 64>) => 32); + test!(#[repr(C, packed( 64))] (u8; elain::Align< 128>) => 64); + test!(#[repr(C, packed( 128))] (u8; elain::Align< 256>) => 128); + test!(#[repr(C, packed( 256))] (u8; elain::Align< 512>) => 256); + test!(#[repr(C, packed( 512))] (u8; elain::Align< 1024>) => 512); + test!(#[repr(C, packed( 1024))] (u8; elain::Align< 2048>) => 1024); + test!(#[repr(C, packed( 2048))] (u8; elain::Align< 4096>) => 2048); + test!(#[repr(C, packed( 4096))] (u8; elain::Align< 8192>) => 4096); + test!(#[repr(C, packed( 8192))] (u8; elain::Align< 16384>) => 8192); + test!(#[repr(C, packed( 16384))] (u8; elain::Align< 32768>) => 16384); + test!(#[repr(C, packed( 32768))] (u8; elain::Align< 65536>) => 32768); + test!(#[repr(C, packed( 65536))] (u8; elain::Align< 131072>) => 65536); /* Alignments above 65536 are not yet supported. - test!(#[repr(C, packed( 131072))] (u8; elain::Align< 262144>) => Some( 131072)); - test!(#[repr(C, packed( 262144))] (u8; elain::Align< 524288>) => Some( 262144)); - test!(#[repr(C, packed( 524288))] (u8; elain::Align< 1048576>) => Some( 524288)); - test!(#[repr(C, packed( 1048576))] (u8; elain::Align< 2097152>) => Some( 1048576)); - test!(#[repr(C, packed( 2097152))] (u8; elain::Align< 4194304>) => Some( 2097152)); - test!(#[repr(C, packed( 4194304))] (u8; elain::Align< 8388608>) => Some( 4194304)); - test!(#[repr(C, packed( 8388608))] (u8; elain::Align< 16777216>) => Some( 8388608)); - test!(#[repr(C, packed( 16777216))] (u8; elain::Align< 33554432>) => Some( 16777216)); - test!(#[repr(C, packed( 33554432))] (u8; elain::Align< 67108864>) => Some( 33554432)); - test!(#[repr(C, packed( 67108864))] (u8; elain::Align< 33554432>) => Some( 67108864)); - test!(#[repr(C, packed( 33554432))] (u8; elain::Align<134217728>) => Some( 33554432)); - test!(#[repr(C, packed(134217728))] (u8; elain::Align<268435456>) => Some(134217728)); - test!(#[repr(C, packed(268435456))] (u8; elain::Align<268435456>) => Some(268435456)); + test!(#[repr(C, packed( 131072))] (u8; elain::Align< 262144>) => 131072); + test!(#[repr(C, packed( 262144))] (u8; elain::Align< 524288>) => 262144); + test!(#[repr(C, packed( 524288))] (u8; elain::Align< 1048576>) => 524288); + test!(#[repr(C, packed( 1048576))] (u8; elain::Align< 2097152>) => 1048576); + test!(#[repr(C, packed( 2097152))] (u8; elain::Align< 4194304>) => 2097152); + test!(#[repr(C, packed( 4194304))] (u8; elain::Align< 8388608>) => 4194304); + test!(#[repr(C, packed( 8388608))] (u8; elain::Align< 16777216>) => 8388608); + test!(#[repr(C, packed( 16777216))] (u8; elain::Align< 33554432>) => 16777216); + test!(#[repr(C, packed( 33554432))] (u8; elain::Align< 67108864>) => 33554432); + test!(#[repr(C, packed( 67108864))] (u8; elain::Align< 33554432>) => 67108864); + test!(#[repr(C, packed( 33554432))] (u8; elain::Align<134217728>) => 33554432); + test!(#[repr(C, packed(134217728))] (u8; elain::Align<268435456>) => 134217728); + test!(#[repr(C, packed(268435456))] (u8; elain::Align<268435456>) => 268435456); */ // Test that `align(N)` does not limit the offset of the trailing field. - test!(#[repr(C, align( 1))] (u8; elain::Align< 2>) => Some( 2)); - test!(#[repr(C, align( 2))] (u8; elain::Align< 4>) => Some( 4)); - test!(#[repr(C, align( 4))] (u8; elain::Align< 8>) => Some( 8)); - test!(#[repr(C, align( 8))] (u8; elain::Align< 16>) => Some( 16)); - test!(#[repr(C, align( 16))] (u8; elain::Align< 32>) => Some( 32)); - test!(#[repr(C, align( 32))] (u8; elain::Align< 64>) => Some( 64)); - test!(#[repr(C, align( 64))] (u8; elain::Align< 128>) => Some( 128)); - test!(#[repr(C, align( 128))] (u8; elain::Align< 256>) => Some( 256)); - test!(#[repr(C, align( 256))] (u8; elain::Align< 512>) => Some( 512)); - test!(#[repr(C, align( 512))] (u8; elain::Align< 1024>) => Some( 1024)); - test!(#[repr(C, align( 1024))] (u8; elain::Align< 2048>) => Some( 2048)); - test!(#[repr(C, align( 2048))] (u8; elain::Align< 4096>) => Some( 4096)); - test!(#[repr(C, align( 4096))] (u8; elain::Align< 8192>) => Some( 8192)); - test!(#[repr(C, align( 8192))] (u8; elain::Align< 16384>) => Some( 16384)); - test!(#[repr(C, align( 16384))] (u8; elain::Align< 32768>) => Some( 32768)); - test!(#[repr(C, align( 32768))] (u8; elain::Align< 65536>) => Some( 65536)); + test!(#[repr(C, align( 1))] (u8; elain::Align< 2>) => 2); + test!(#[repr(C, align( 2))] (u8; elain::Align< 4>) => 4); + test!(#[repr(C, align( 4))] (u8; elain::Align< 8>) => 8); + test!(#[repr(C, align( 8))] (u8; elain::Align< 16>) => 16); + test!(#[repr(C, align( 16))] (u8; elain::Align< 32>) => 32); + test!(#[repr(C, align( 32))] (u8; elain::Align< 64>) => 64); + test!(#[repr(C, align( 64))] (u8; elain::Align< 128>) => 128); + test!(#[repr(C, align( 128))] (u8; elain::Align< 256>) => 256); + test!(#[repr(C, align( 256))] (u8; elain::Align< 512>) => 512); + test!(#[repr(C, align( 512))] (u8; elain::Align< 1024>) => 1024); + test!(#[repr(C, align( 1024))] (u8; elain::Align< 2048>) => 2048); + test!(#[repr(C, align( 2048))] (u8; elain::Align< 4096>) => 4096); + test!(#[repr(C, align( 4096))] (u8; elain::Align< 8192>) => 8192); + test!(#[repr(C, align( 8192))] (u8; elain::Align< 16384>) => 16384); + test!(#[repr(C, align( 16384))] (u8; elain::Align< 32768>) => 32768); + test!(#[repr(C, align( 32768))] (u8; elain::Align< 65536>) => 65536); /* Alignments above 65536 are not yet supported. - test!(#[repr(C, align( 65536))] (u8; elain::Align< 131072>) => Some( 131072)); - test!(#[repr(C, align( 131072))] (u8; elain::Align< 262144>) => Some( 262144)); - test!(#[repr(C, align( 262144))] (u8; elain::Align< 524288>) => Some( 524288)); - test!(#[repr(C, align( 524288))] (u8; elain::Align< 1048576>) => Some( 1048576)); - test!(#[repr(C, align( 1048576))] (u8; elain::Align< 2097152>) => Some( 2097152)); - test!(#[repr(C, align( 2097152))] (u8; elain::Align< 4194304>) => Some( 4194304)); - test!(#[repr(C, align( 4194304))] (u8; elain::Align< 8388608>) => Some( 8388608)); - test!(#[repr(C, align( 8388608))] (u8; elain::Align< 16777216>) => Some( 16777216)); - test!(#[repr(C, align( 16777216))] (u8; elain::Align< 33554432>) => Some( 33554432)); - test!(#[repr(C, align( 33554432))] (u8; elain::Align< 67108864>) => Some( 67108864)); - test!(#[repr(C, align( 67108864))] (u8; elain::Align< 33554432>) => Some( 33554432)); - test!(#[repr(C, align( 33554432))] (u8; elain::Align<134217728>) => Some(134217728)); - test!(#[repr(C, align(134217728))] (u8; elain::Align<268435456>) => Some(268435456)); + test!(#[repr(C, align( 65536))] (u8; elain::Align< 131072>) => 131072); + test!(#[repr(C, align( 131072))] (u8; elain::Align< 262144>) => 262144); + test!(#[repr(C, align( 262144))] (u8; elain::Align< 524288>) => 524288); + test!(#[repr(C, align( 524288))] (u8; elain::Align< 1048576>) => 1048576); + test!(#[repr(C, align( 1048576))] (u8; elain::Align< 2097152>) => 2097152); + test!(#[repr(C, align( 2097152))] (u8; elain::Align< 4194304>) => 4194304); + test!(#[repr(C, align( 4194304))] (u8; elain::Align< 8388608>) => 8388608); + test!(#[repr(C, align( 8388608))] (u8; elain::Align< 16777216>) => 16777216); + test!(#[repr(C, align( 16777216))] (u8; elain::Align< 33554432>) => 33554432); + test!(#[repr(C, align( 33554432))] (u8; elain::Align< 67108864>) => 67108864); + test!(#[repr(C, align( 67108864))] (u8; elain::Align< 33554432>) => 33554432); + test!(#[repr(C, align( 33554432))] (u8; elain::Align<134217728>) => 134217728); + test!(#[repr(C, align(134217728))] (u8; elain::Align<268435456>) => 268435456); */ } @@ -576,37 +635,37 @@ mod tests { #[test] fn test_align_of_dst() { // Test that `align_of!` correctly computes the alignment of DSTs. - assert_eq!(align_of!([elain::Align<1>]), Some(1)); - assert_eq!(align_of!([elain::Align<2>]), Some(2)); - assert_eq!(align_of!([elain::Align<4>]), Some(4)); - assert_eq!(align_of!([elain::Align<8>]), Some(8)); - assert_eq!(align_of!([elain::Align<16>]), Some(16)); - assert_eq!(align_of!([elain::Align<32>]), Some(32)); - assert_eq!(align_of!([elain::Align<64>]), Some(64)); - assert_eq!(align_of!([elain::Align<128>]), Some(128)); - assert_eq!(align_of!([elain::Align<256>]), Some(256)); - assert_eq!(align_of!([elain::Align<512>]), Some(512)); - assert_eq!(align_of!([elain::Align<1024>]), Some(1024)); - assert_eq!(align_of!([elain::Align<2048>]), Some(2048)); - assert_eq!(align_of!([elain::Align<4096>]), Some(4096)); - assert_eq!(align_of!([elain::Align<8192>]), Some(8192)); - assert_eq!(align_of!([elain::Align<16384>]), Some(16384)); - assert_eq!(align_of!([elain::Align<32768>]), Some(32768)); - assert_eq!(align_of!([elain::Align<65536>]), Some(65536)); + assert_eq!(align_of!([elain::Align<1>]).get(), 1); + assert_eq!(align_of!([elain::Align<2>]).get(), 2); + assert_eq!(align_of!([elain::Align<4>]).get(), 4); + assert_eq!(align_of!([elain::Align<8>]).get(), 8); + assert_eq!(align_of!([elain::Align<16>]).get(), 16); + assert_eq!(align_of!([elain::Align<32>]).get(), 32); + assert_eq!(align_of!([elain::Align<64>]).get(), 64); + assert_eq!(align_of!([elain::Align<128>]).get(), 128); + assert_eq!(align_of!([elain::Align<256>]).get(), 256); + assert_eq!(align_of!([elain::Align<512>]).get(), 512); + assert_eq!(align_of!([elain::Align<1024>]).get(), 1024); + assert_eq!(align_of!([elain::Align<2048>]).get(), 2048); + assert_eq!(align_of!([elain::Align<4096>]).get(), 4096); + assert_eq!(align_of!([elain::Align<8192>]).get(), 8192); + assert_eq!(align_of!([elain::Align<16384>]).get(), 16384); + assert_eq!(align_of!([elain::Align<32768>]).get(), 32768); + assert_eq!(align_of!([elain::Align<65536>]).get(), 65536); /* Alignments above 65536 are not yet supported. - assert_eq!(align_of!([elain::Align<131072>]), Some(131072)); - assert_eq!(align_of!([elain::Align<262144>]), Some(262144)); - assert_eq!(align_of!([elain::Align<524288>]), Some(524288)); - assert_eq!(align_of!([elain::Align<1048576>]), Some(1048576)); - assert_eq!(align_of!([elain::Align<2097152>]), Some(2097152)); - assert_eq!(align_of!([elain::Align<4194304>]), Some(4194304)); - assert_eq!(align_of!([elain::Align<8388608>]), Some(8388608)); - assert_eq!(align_of!([elain::Align<16777216>]), Some(16777216)); - assert_eq!(align_of!([elain::Align<33554432>]), Some(33554432)); - assert_eq!(align_of!([elain::Align<67108864>]), Some(67108864)); - assert_eq!(align_of!([elain::Align<33554432>]), Some(33554432)); - assert_eq!(align_of!([elain::Align<134217728>]), Some(134217728)); - assert_eq!(align_of!([elain::Align<268435456>]), Some(268435456)); + assert_eq!(align_of!([elain::Align<131072>]).get(), 131072); + assert_eq!(align_of!([elain::Align<262144>]).get(), 262144); + assert_eq!(align_of!([elain::Align<524288>]).get(), 524288); + assert_eq!(align_of!([elain::Align<1048576>]).get(), 1048576); + assert_eq!(align_of!([elain::Align<2097152>]).get(), 2097152); + assert_eq!(align_of!([elain::Align<4194304>]).get(), 4194304); + assert_eq!(align_of!([elain::Align<8388608>]).get(), 8388608); + assert_eq!(align_of!([elain::Align<16777216>]).get(), 16777216); + assert_eq!(align_of!([elain::Align<33554432>]).get(), 33554432); + assert_eq!(align_of!([elain::Align<67108864>]).get(), 67108864); + assert_eq!(align_of!([elain::Align<33554432>]).get(), 33554432); + assert_eq!(align_of!([elain::Align<134217728>]).get(), 134217728); + assert_eq!(align_of!([elain::Align<268435456>]).get(), 268435456); */ } diff --git a/zerocopy-derive/src/ext.rs b/zerocopy-derive/src/ext.rs index 87cf838f88..d2012986f0 100644 --- a/zerocopy-derive/src/ext.rs +++ b/zerocopy-derive/src/ext.rs @@ -6,12 +6,18 @@ // This file may not be copied, modified, or distributed except according to // those terms. -use syn::{Data, DataEnum, DataStruct, DataUnion, Type}; +use proc_macro2::Span; +use syn::{Data, DataEnum, DataStruct, DataUnion, Member, Type}; pub trait DataExt { /// Extract the types of all fields. For enums, extract the types of fields /// from each variant. fn field_types(&self) -> Vec<&Type>; + + /// Extract the single trailing field of a struct, if any. + fn trailing_field(&self) -> Option<(syn::Member, &Type)> { + None + } } impl DataExt for Data { @@ -22,12 +28,30 @@ impl DataExt for Data { Data::Union(un) => un.field_types(), } } + + fn trailing_field(&self) -> Option<(syn::Member, &Type)> { + match self { + Data::Struct(strc) => strc.trailing_field(), + Data::Enum(enm) => enm.trailing_field(), + Data::Union(un) => un.trailing_field(), + } + } } impl DataExt for DataStruct { fn field_types(&self) -> Vec<&Type> { self.fields.iter().map(|f| &f.ty).collect() } + + fn trailing_field(&self) -> Option<(Member, &Type)> { + let (index, trailing_field) = self.fields.iter().enumerate().last()?; + let member = if let Some(ident) = &trailing_field.ident { + Member::Named(ident.to_owned()) + } else { + Member::Unnamed(syn::Index { index: index as u32, span: Span::call_site() }) + }; + Some((member, &trailing_field.ty)) + } } impl DataExt for DataEnum { diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs index 2d7ec65427..c740308bda 100644 --- a/zerocopy-derive/src/lib.rs +++ b/zerocopy-derive/src/lib.rs @@ -42,6 +42,17 @@ use { use {crate::ext::*, crate::repr::*}; +// Unwraps a `Result<_, Vec>`, 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: // @@ -62,10 +73,72 @@ use {crate::ext::*, crate::repr::*}; #[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::(&ast.attrs)); + let is_repr_c = reprs.iter().any(|(_meta, repr)| repr == &StructRepr::C); + is_repr_c + } + Data::Enum(..) | Data::Union(..) => false, + }; + + let layout = if let (Some((name, ty)), false) = (ast.data.trailing_field(), is_repr_c_struct) { + // SAFETY: This call requires that the type is a `repr(C)` struct, which + // is ensured by `is_repr_c_struct`. + quote!(::zerocopy::repr_c_dst_layout!( + Self, + #name: #ty + )) + } else { + // For enums, unions, and non-`repr(C)` structs, we require that `Self` + // is sized. + quote!(::zerocopy::DstLayout::for_type::()) + }; + + let extras = Some({ + quote!( + const LAYOUT: ::zerocopy::DstLayout = #layout; + + // 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: ::core::ptr::NonNull, + _elems: usize, + ) -> ::core::ptr::NonNull { + bytes.cast::() + } + ) + }); + match &ast.data { - Data::Struct(strct) => impl_block(&ast, strct, Trait::KnownLayout, false, None), - Data::Enum(enm) => impl_block(&ast, enm, Trait::KnownLayout, false, None), - Data::Union(unn) => impl_block(&ast, unn, Trait::KnownLayout, false, None), + Data::Struct(strct) => { + // 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, + RequireBoundedFields::Trailing, + None, + 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, None, 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, None, extras) + } } .into() } @@ -114,17 +187,6 @@ pub fn derive_unaligned(ts: proc_macro::TokenStream) -> proc_macro::TokenStream .into() } -// Unwraps a `Result<_, Vec>`, 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], @@ -136,7 +198,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, Trait::FromZeroes, true, None) + impl_block(ast, strct, Trait::FromZeroes, RequireBoundedFields::Yes, None, None) } // An enum is `FromZeroes` if: @@ -170,21 +232,21 @@ fn derive_from_zeroes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::To .to_compile_error(); } - impl_block(ast, enm, Trait::FromZeroes, true, None) + impl_block(ast, enm, Trait::FromZeroes, RequireBoundedFields::Yes, 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, Trait::FromZeroes, true, None) + impl_block(ast, unn, Trait::FromZeroes, RequireBoundedFields::Yes, 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, Trait::FromBytes, true, None) + impl_block(ast, strct, Trait::FromBytes, RequireBoundedFields::Yes, None, None) } // An enum is `FromBytes` if: @@ -227,7 +289,7 @@ fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Tok .to_compile_error(); } - impl_block(ast, enm, Trait::FromBytes, true, None) + impl_block(ast, enm, Trait::FromBytes, RequireBoundedFields::Yes, None, None) } #[rustfmt::skip] @@ -258,7 +320,7 @@ const ENUM_FROM_BYTES_CFG: Config = { // - all fields are `FromBytes` fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { - impl_block(ast, unn, Trait::FromBytes, true, None) + impl_block(ast, unn, Trait::FromBytes, RequireBoundedFields::Yes, None, None) } // A struct is `AsBytes` if: @@ -292,7 +354,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, Trait::AsBytes, true, padding_check) + impl_block(ast, strct, Trait::AsBytes, RequireBoundedFields::Yes, padding_check, None) } const STRUCT_UNION_AS_BYTES_CFG: Config = Config { @@ -315,7 +377,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 = try_or_print!(ENUM_AS_BYTES_CFG.validate_reprs(ast)); - impl_block(ast, enm, Trait::AsBytes, false, None) + impl_block(ast, enm, Trait::AsBytes, RequireBoundedFields::No, None, None) } #[rustfmt::skip] @@ -357,7 +419,7 @@ 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, Trait::AsBytes, true, Some(PaddingCheck::Union)) + impl_block(ast, unn, Trait::AsBytes, RequireBoundedFields::Yes, Some(PaddingCheck::Union), None) } // A struct is `Unaligned` if: @@ -368,9 +430,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_bounds_on_field_types = !reprs.contains(&StructRepr::Packed); + let require_trait_bounds_on_field_types = (!reprs.contains(&StructRepr::Packed)).into(); - impl_block(ast, strct, Trait::Unaligned, require_trait_bounds_on_field_types, None) + impl_block(ast, strct, Trait::Unaligned, require_trait_bounds_on_field_types, None, None) } const STRUCT_UNION_UNALIGNED_CFG: Config = Config { @@ -401,7 +463,7 @@ fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Toke // 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, true, None) + impl_block(ast, enm, Trait::Unaligned, RequireBoundedFields::Yes, None, None) } #[rustfmt::skip] @@ -437,9 +499,9 @@ const ENUM_UNALIGNED_CFG: Config = { 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_on_field_types = !reprs.contains(&StructRepr::Packed); + let require_trait_bound_on_field_types = (!reprs.contains(&StructRepr::Packed)).into(); - impl_block(ast, unn, Trait::Unaligned, require_trait_bound_on_field_types, None) + impl_block(ast, unn, Trait::Unaligned, require_trait_bound_on_field_types, None, None) } // This enum describes what kind of padding check needs to be generated for the @@ -479,12 +541,29 @@ impl Trait { } } +#[derive(Debug, Eq, PartialEq)] +enum RequireBoundedFields { + No, + Yes, + Trailing, +} + +impl From for RequireBoundedFields { + fn from(do_require: bool) -> Self { + match do_require { + true => Self::Yes, + false => Self::No, + } + } +} + fn impl_block( input: &DeriveInput, data: &D, trt: Trait, - require_trait_bound_on_field_types: bool, + require_trait_bound_on_field_types: RequireBoundedFields, padding_check: Option, + extras: Option, ) -> proc_macro2::TokenStream { // In this documentation, we will refer to this hypothetical struct: // @@ -548,11 +627,12 @@ fn impl_block( let trait_ident = trt.ident(); let field_types = data.field_types(); - let field_type_bounds = require_trait_bound_on_field_types - .then(|| field_types.iter().map(|ty| parse_quote!(#ty: ::zerocopy::#trait_ident))) - .into_iter() - .flatten() - .collect::>(); + 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 @@ -575,28 +655,6 @@ fn impl_block( .chain(field_type_bounds.iter()) .chain(padding_check_bound.iter()); - let layout_extras = if trt == Trait::KnownLayout { - // We currently only support deriving for sized types; this code will - // fail to compile for unsized types. - Some(quote!( - const LAYOUT: ::zerocopy::DstLayout = ::zerocopy::DstLayout::for_type::(); - - // 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: ::core::ptr::NonNull, - _elems: usize, - ) -> ::core::ptr::NonNull { - bytes.cast::() - } - )) - } else { - None - }; - // The parameters with trait bounds, but without type defaults. let params = input.generics.params.clone().into_iter().map(|mut param| { match &mut param { @@ -626,7 +684,7 @@ fn impl_block( { fn only_derive_is_allowed_to_implement_this_trait() {} - #layout_extras + #extras } } } diff --git a/zerocopy-derive/src/repr.rs b/zerocopy-derive/src/repr.rs index dc34fd6397..54702e4d8c 100644 --- a/zerocopy-derive/src/repr.rs +++ b/zerocopy-derive/src/repr.rs @@ -248,7 +248,7 @@ impl Display for Repr { } } -fn reprs(attrs: &[Attribute]) -> Result, Vec> { +pub(crate) fn reprs(attrs: &[Attribute]) -> Result, Vec> { let mut reprs = Vec::new(); let mut errors = Vec::new(); for attr in attrs {