From dce24809f9fe36349be236a556106dcb01d434f7 Mon Sep 17 00:00:00 2001 From: Josh Liebow-Feeser Date: Tue, 13 Oct 2026 17:00:43 -0400 Subject: [PATCH] Check reconstructed layouts against compiler layout queries Reconstructed layouts must agree with rustc's objects. Check that boundary for ordinary, aligned, and packed repr(C) fixtures: compile-time assertions compare complete size, alignment, and field offsets. Nested DST fixtures safely unsize real array tails and compare the metadata-size calculation with size_of_val. The checks cover the selected compiler, target, and concrete instantiations; they do not establish a universal result across types or compilers. MSRV runtime comparisons remain available where layout queries are not yet const. Agent-authored-by: AI agent acting on joshlf's behalf gherrit-pr-id: Gkxoay4mfhudcnfy2cuk46iu6gnltvxg3 --- zerocopy/src/layout.rs | 106 +++++++++++++++++++++++++++++++++++++++++ zerocopy/src/lib.rs | 61 ++++++++++++++++++++---- 2 files changed, 157 insertions(+), 10 deletions(-) diff --git a/zerocopy/src/layout.rs b/zerocopy/src/layout.rs index 067fd5702e..fc2be0426a 100644 --- a/zerocopy/src/layout.rs +++ b/zerocopy/src/layout.rs @@ -2332,6 +2332,112 @@ mod tests { use super::*; use crate::PointerMetadata as _; + // The MSRV's dead-code analysis does not follow uses in unnamed consts. + #[allow(dead_code)] + const fn sized_size(layout: DstLayout) -> usize { + match layout.size_info { + SizeInfo::Sized { size } => size, + SizeInfo::SliceDst(_) => const_panic!("expected a sized layout"), + } + } + + // Compare reconstructed layouts directly with rustc's size and alignment + // of the complete type. + #[allow(dead_code)] + const fn assert_compiler_layout(layout: DstLayout) { + const_assert!(layout.align.get() == mem::align_of::()); + const_assert!(sized_size(layout) == mem::size_of::()); + } + + // Keep the type declaration, ordered field reconstruction, and compiler + // comparisons together. Evaluating the associated constants below checks + // those concrete generic instantiations without generic const expressions + // on the MSRV. + macro_rules! assert_repr_c_layout { + ($name:ident, $repr:meta, $align:expr, $packed:expr) => { + #[derive(crate::KnownLayout)] + #[$repr] + struct $name { + prefix: u8, + data: [T; N], + tag: u16, + tail: T, + } + + impl $name { + #[allow(dead_code)] + const ASSERT_LAYOUT: () = { + let reconstructed = DstLayout::for_repr_c_struct( + $align, + $packed, + &[ + DstLayout::for_type::(), + DstLayout::for_type::<[T; N]>(), + DstLayout::for_type::(), + DstLayout::for_type::(), + ], + ); + assert_compiler_layout::(reconstructed); + assert_compiler_layout::(::LAYOUT); + }; + + // Older compilers miss uses through the const assertions below. + #[allow(dead_code)] + #[rustversion::since(1.77)] + const ASSERT_OFFSETS: () = { + let prefix = + DstLayout::new_zst($align).extend(DstLayout::for_type::(), $packed); + let data = prefix.extend(DstLayout::for_type::<[T; N]>(), $packed); + let tag = data.extend(DstLayout::for_type::(), $packed); + let tail = tag.extend(DstLayout::for_type::(), $packed); + const_assert!(mem::offset_of!(Self, prefix) == 0); + // `extend` has not added trailing padding, so subtracting + // the last field's size gives its reconstructed offset. + #[allow(clippy::arithmetic_side_effects)] + { + const_assert!( + mem::offset_of!(Self, data) + == sized_size(data) - mem::size_of::<[T; N]>() + ); + const_assert!( + mem::offset_of!(Self, tag) == sized_size(tag) - mem::size_of::() + ); + const_assert!( + mem::offset_of!(Self, tail) == sized_size(tail) - mem::size_of::() + ); + } + }; + } + + const _: () = $name::::ASSERT_LAYOUT; + const _: () = $name::<[u8; 3], 2>::ASSERT_LAYOUT; + const _: () = $name::<(), 0>::ASSERT_LAYOUT; + + #[rustversion::since(1.77)] + const _: () = $name::::ASSERT_OFFSETS; + #[rustversion::since(1.77)] + const _: () = $name::<[u8; 3], 2>::ASSERT_OFFSETS; + #[rustversion::since(1.77)] + const _: () = $name::<(), 0>::ASSERT_OFFSETS; + }; + } + + assert_repr_c_layout!(NativeC, repr(C), None, None); + assert_repr_c_layout!(NativeAlignedC, repr(C, align(16)), NonZeroUsize::new(16), None); + assert_repr_c_layout!(NativePackedC, repr(C, packed(2)), None, NonZeroUsize::new(2)); + + #[derive(crate::KnownLayout, crate::Unaligned)] + #[repr(C)] + struct UnalignedByte { + byte: u8, + } + + const _: () = { + const_assert!(mem::align_of::() == 1); + const_assert!(mem::align_of::() == 1); + assert_compiler_layout::(::LAYOUT); + }; + /// Models the size of nested `repr(C)` structs ending in a slice. /// /// `leading` lists structs from outermost to innermost. Each tuple gives diff --git a/zerocopy/src/lib.rs b/zerocopy/src/lib.rs index 6040d6e0dd..49880c2ceb 100644 --- a/zerocopy/src/lib.rs +++ b/zerocopy/src/lib.rs @@ -6772,6 +6772,44 @@ mod tests { inner: ManuallyDrop>, } + // `size_of_val` and `align_of_val` became const in 1.85. Expected sizes and + // alignments come from references safely unsized from real sized objects. + #[rustversion::since(1.85)] + const _: () = { + macro_rules! assert_native_dst_layout { + ($elems:expr) => {{ + let value = NestedPackedDstOuter { + z: 0, + inner: ManuallyDrop::new(NestedPackedDstInner { + a: NestedPackedDstWord([0; 4]), + b: 0, + tail: [0; $elems], + }), + }; + let value: &NestedPackedDstOuter<[u8]> = &value; + let layout = NestedPackedDstOuter::<[u8]>::LAYOUT; + const_assert!(layout.align.get() == mem::align_of_val(value)); + match layout.size_info { + // This is the const implementation called by + // `PointerMetadata::size_for_metadata` for slice DSTs. + SizeInfo::SliceDst(trailing) => match trailing.size_for_elems($elems) { + Some(size) => const_assert!(size == mem::size_of_val(value)), + None => const_panic!("native DST size must fit in usize"), + }, + SizeInfo::Sized { .. } => const_panic!("expected a slice DST layout"), + } + }}; + } + + assert_native_dst_layout!(0); + assert_native_dst_layout!(1); + assert_native_dst_layout!(2); + assert_native_dst_layout!(3); + assert_native_dst_layout!(4); + assert_native_dst_layout!(7); + assert_native_dst_layout!(8); + }; + #[repr(C, align(2))] struct Align2(T); @@ -6877,7 +6915,7 @@ mod tests { #[test] fn test_nested_packed_dst_layout() { - fn assert_native_size(expected: usize) { + fn assert_native_size() { let value = NestedPackedDstOuter { z: 0, inner: ManuallyDrop::new(NestedPackedDstInner { @@ -6887,17 +6925,20 @@ mod tests { }), }; let value: &NestedPackedDstOuter<[u8]> = &value; - assert_eq!(mem::size_of_val(value), expected); - assert_eq!(NestedPackedDstOuter::<[u8]>::size_for_metadata(N), Some(expected)); + assert_eq!( + NestedPackedDstOuter::<[u8]>::size_for_metadata(N), + Some(mem::size_of_val(value)) + ); + assert_eq!(NestedPackedDstOuter::<[u8]>::LAYOUT.align.get(), mem::align_of_val(value)); } - assert_native_size::<0>(10); - assert_native_size::<1>(10); - assert_native_size::<2>(10); - assert_native_size::<3>(10); - assert_native_size::<4>(14); - assert_native_size::<7>(14); - assert_native_size::<8>(18); + assert_native_size::<0>(); + assert_native_size::<1>(); + assert_native_size::<2>(); + assert_native_size::<3>(); + assert_native_size::<4>(); + assert_native_size::<7>(); + assert_native_size::<8>(); let layout = NestedPackedDstOuter::<[u8]>::LAYOUT; assert_eq!(layout.align.get(), 2);