Skip to content
Open
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
106 changes: 106 additions & 0 deletions zerocopy/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(layout: DstLayout) {
const_assert!(layout.align.get() == mem::align_of::<T>());
const_assert!(sized_size(layout) == mem::size_of::<T>());
}

// 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<T, const N: usize> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow the generated layout fixtures to be dead code

With the repository's RUSTFLAGS=-Dwarnings test matrix, these fixtures fail to compile on supported older toolchains. Before Rust 1.77 the ASSERT_OFFSETS items are removed entirely, and through Rust 1.88 offset_of! does not make the type count as constructed; the layout constants only use the type and therefore trigger dead_code for every macro invocation. Put #[allow(dead_code)] on the generated struct, rather than only on ASSERT_LAYOUT, so the MSRV and intermediate-version jobs can compile the tests.

Useful? React with 👍 / 👎.

prefix: u8,
data: [T; N],
tag: u16,
tail: T,
}

impl<T: crate::KnownLayout, const N: usize> $name<T, N> {
#[allow(dead_code)]
const ASSERT_LAYOUT: () = {
let reconstructed = DstLayout::for_repr_c_struct(
$align,
$packed,
&[
DstLayout::for_type::<u8>(),
DstLayout::for_type::<[T; N]>(),
DstLayout::for_type::<u16>(),
DstLayout::for_type::<T>(),
],
);
assert_compiler_layout::<Self>(reconstructed);
assert_compiler_layout::<Self>(<Self as crate::KnownLayout>::LAYOUT);
};

// Older compilers miss uses through the const assertions below.
#[allow(dead_code)]
#[rustversion::since(1.77)]
const ASSERT_OFFSETS: () = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow the offset assertion constant to be dead code

On the supported Rust 1.77–1.88 range, dead-code analysis does not follow the uses of this associated constant from the unnamed const items below, so ASSERT_OFFSETS is reported as unused and the repository's -Dwarnings CI jobs fail. Apply the same #[allow(dead_code)] used on ASSERT_LAYOUT to this version-gated constant.

Useful? React with 👍 / 👎.

let prefix =
DstLayout::new_zst($align).extend(DstLayout::for_type::<u8>(), $packed);
let data = prefix.extend(DstLayout::for_type::<[T; N]>(), $packed);
let tag = data.extend(DstLayout::for_type::<u16>(), $packed);
let tail = tag.extend(DstLayout::for_type::<T>(), $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::<u16>()
);
const_assert!(
mem::offset_of!(Self, tail) == sized_size(tail) - mem::size_of::<T>()
);
}
};
}

const _: () = $name::<u64, 3>::ASSERT_LAYOUT;
const _: () = $name::<[u8; 3], 2>::ASSERT_LAYOUT;
const _: () = $name::<(), 0>::ASSERT_LAYOUT;

#[rustversion::since(1.77)]
const _: () = $name::<u64, 3>::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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow the unconstructed unaligned fixture

The CI matrix compiles tests with -Dwarnings, but UnalignedByte is never constructed and its byte field is never read; deriving traits and querying its size/alignment are ignored by dead-code analysis for this purpose. This consequently produces a hard dead_code error, so this fixture also needs a targeted #[allow(dead_code)] or a real construction/read.

Useful? React with 👍 / 👎.

byte: u8,
}

const _: () = {
const_assert!(mem::align_of::<u8>() == 1);
const_assert!(mem::align_of::<UnalignedByte>() == 1);
assert_compiler_layout::<UnalignedByte>(<UnalignedByte as crate::KnownLayout>::LAYOUT);
};

/// Models the size of nested `repr(C)` structs ending in a slice.
///
/// `leading` lists structs from outermost to innermost. Each tuple gives
Expand Down
61 changes: 51 additions & 10 deletions zerocopy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6772,6 +6772,44 @@ mod tests {
inner: ManuallyDrop<NestedPackedDstInner<T>>,
}

// `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>(T);

Expand Down Expand Up @@ -6877,7 +6915,7 @@ mod tests {

#[test]
fn test_nested_packed_dst_layout() {
fn assert_native_size<const N: usize>(expected: usize) {
fn assert_native_size<const N: usize>() {
let value = NestedPackedDstOuter {
z: 0,
inner: ManuallyDrop::new(NestedPackedDstInner {
Expand All @@ -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);
Expand Down
Loading