musli-zerocopy 0.1.4

Refreshingly simple zero copy primitives by Müsli.
Documentation
//! Tests that the padding and validation code generated by the `ZeroCopy`
//! derive agrees with the layout the compiler picks for a type.

use core::mem::{offset_of, size_of};

use alloc::string::ToString;

use anyhow::Result;

use crate::{OwnedBuf, Ref, ZeroCopy};

/// Load a `T` from `bytes` placed at offset 0 of a buffer aligned to 8 bytes.
///
/// The buffer is aligned beyond what `T` requires so that the address of any
/// nested field is aligned the same way as its offset into `T`.
fn load_bytes<T>(bytes: &[u8]) -> Result<T, crate::Error>
where
    T: ZeroCopy + Copy,
{
    let mut buf = OwnedBuf::with_alignment::<u64>();
    buf.extend_from_slice(bytes).unwrap();
    buf.load(Ref::<T>::new(0u32)).copied()
}

/// In `repr(packed(N))` each field is aligned to `min(N, align_of::<F>())`, so
/// fields with a smaller alignment than `N` keep their natural alignment.
#[derive(Clone, Copy, ZeroCopy)]
#[repr(C, packed(4))]
#[zero_copy(crate)]
struct PackedBool {
    a: u8,
    b: bool,
    c: u16,
    d: u8,
}

#[test]
fn packed_n_layout() {
    assert_eq!(offset_of!(PackedBool, a), 0);
    assert_eq!(offset_of!(PackedBool, b), 1);
    assert_eq!(offset_of!(PackedBool, c), 2);
    assert_eq!(offset_of!(PackedBool, d), 4);
    assert_eq!(size_of::<PackedBool>(), 6);

    assert_eq!(offset_of!(PackedPad, a), 0);
    assert_eq!(offset_of!(PackedPad, b), 1);
    assert_eq!(offset_of!(PackedPad, c), 4);
    assert_eq!(size_of::<PackedPad>(), 8);
}

#[test]
fn packed_n_validate_rejects_invalid_field() {
    // `b` holds 2, which is not a valid bool.
    let Err(error) = load_bytes::<PackedBool>(&[0, 2, 0, 0, 0, 0]) else {
        panic!("expected an invalid bool error");
    };
    assert_eq!(error.to_string(), "Invalid bool representation 2");
}

#[test]
fn packed_n_validate_accepts_valid_field() {
    // `b` is valid, `d` is an arbitrary byte which must not be validated as a
    // bool.
    let value = load_bytes::<PackedBool>(&[7, 1, 0x34, 0x12, 2, 0]).unwrap();
    let (a, b, d) = (value.a, value.b, value.d);
    assert_eq!((a, b, d), (7, true, 2));
}

#[derive(Clone, Copy, ZeroCopy)]
#[repr(C, packed(4))]
#[zero_copy(crate)]
struct PackedPad {
    a: u8,
    b: u8,
    c: u32,
}

#[test]
fn packed_n_pad_preserves_fields() -> Result<()> {
    const _: () = assert!(PackedPad::PADDED);

    let mut buf = OwnedBuf::new();
    buf.store(&PackedPad {
        a: 1,
        b: 2,
        c: u32::from_ne_bytes([3, 4, 5, 6]),
    })?;

    assert_eq!(buf.as_slice(), &[1, 2, 0, 0, 3, 4, 5, 6]);
    Ok(())
}

#[derive(Clone, Copy, ZeroCopy)]
#[repr(C)]
#[zero_copy(crate)]
struct Inner {
    x: u16,
    y: bool,
}

/// `Inner` is stored at offset 1, so its fields are only aligned relative to
/// the start of `Inner`.
#[derive(Clone, Copy, ZeroCopy)]
#[repr(C, packed)]
#[zero_copy(crate)]
struct PackedInner {
    a: u8,
    inner: Inner,
}

#[test]
fn nested_in_packed_layout() {
    assert_eq!(offset_of!(Inner, x), 0);
    assert_eq!(offset_of!(Inner, y), 2);
    assert_eq!(size_of::<Inner>(), 4);
    assert_eq!(offset_of!(PackedInner, inner), 1);
    assert_eq!(size_of::<PackedInner>(), 5);

    assert_eq!(offset_of!(PackedEnum, e), 1);
    assert_eq!(size_of::<Enum>(), 6);
    assert_eq!(size_of::<PackedEnum>(), 7);

    assert_eq!(offset_of!(PackedEnum16, e), 1);
    assert_eq!(size_of::<PackedEnum16>(), 4);
}

#[test]
fn nested_struct_in_packed_rejects_invalid_field() {
    // `inner.y` sits at offset 1 + 2 and holds 2.
    let Err(error) = load_bytes::<PackedInner>(&[0, 0, 0, 2, 0]) else {
        panic!("expected an invalid bool error");
    };
    assert_eq!(error.to_string(), "Invalid bool representation 2");
}

#[test]
fn nested_struct_in_packed_accepts_valid_field() {
    // The last byte is trailing padding of `Inner` and is not validated.
    let value = load_bytes::<PackedInner>(&[0, 0, 0, 1, 2]).unwrap();
    let inner = value.inner;
    assert!(inner.y);
}

#[derive(Clone, Copy, ZeroCopy)]
#[repr(u8)]
#[zero_copy(crate)]
enum Enum {
    A(u16, bool),
}

#[derive(Clone, Copy, ZeroCopy)]
#[repr(C, packed)]
#[zero_copy(crate)]
struct PackedEnum {
    a: u8,
    e: Enum,
}

#[test]
fn nested_enum_in_packed_rejects_invalid_field() {
    // The bool of `Enum::A` sits at offset 1 + 4 and holds 2.
    let Err(error) = load_bytes::<PackedEnum>(&[0, 0, 0, 0, 0, 2, 0]) else {
        panic!("expected an invalid bool error");
    };
    assert_eq!(error.to_string(), "Invalid bool representation 2");
}

#[test]
fn nested_enum_in_packed_accepts_valid_field() {
    // The high byte of the `u16` holds 2, which must not be validated as a
    // bool.
    let value = load_bytes::<PackedEnum>(&[0, 0, 0, 0, 2, 1, 0]).unwrap();
    let Enum::A(_, b) = value.e;
    assert!(b);
}

#[derive(Clone, Copy, PartialEq, Eq, Debug, ZeroCopy)]
#[repr(u16)]
#[zero_copy(crate)]
enum Enum16 {
    A = 0,
    B = 1,
}

#[derive(Clone, Copy, ZeroCopy)]
#[repr(C, packed)]
#[zero_copy(crate)]
struct PackedEnum16 {
    a: u8,
    e: Enum16,
    b: u8,
}

#[test]
fn nested_enum_discriminant_in_packed() {
    // The discriminant sits unaligned at offset 1.
    let [d0, d1] = 1u16.to_ne_bytes();
    let value = load_bytes::<PackedEnum16>(&[0, d0, d1, 0xff]).unwrap();
    let e = value.e;
    assert_eq!(e, Enum16::B);

    let [d0, d1] = 5u16.to_ne_bytes();
    assert!(load_bytes::<PackedEnum16>(&[0, d0, d1, 0]).is_err());
}

#[test]
fn unaligned_load_validates_relative_to_value() -> Result<()> {
    let mut buf = OwnedBuf::with_alignment::<u64>();

    // `Inner` stored at offset 1, with `y` holding 2.
    buf.extend_from_slice(&[0, 0, 0, 2, 0])?;
    let Err(error) = buf.load_at_unaligned::<Inner>(1) else {
        panic!("expected an invalid bool error");
    };
    assert_eq!(error.to_string(), "Invalid bool representation 2");

    // `Inner` stored at offset 1 with `y` holding 1, and 2 in its padding.
    buf.clear();
    buf.extend_from_slice(&[0, 0, 0, 1, 2])?;
    assert!(buf.load_at_unaligned::<Inner>(1)?.y);
    Ok(())
}

/// A `Ref` validates its fields through loads which must not assume that the
/// `Ref` itself is aligned.
#[derive(Clone, Copy, ZeroCopy)]
#[repr(C, packed)]
#[zero_copy(crate)]
struct PackedRef {
    a: u8,
    r: Ref<[u8]>,
}

#[test]
fn nested_ref_in_packed() -> Result<()> {
    assert_eq!(offset_of!(PackedRef, r), 1);

    let mut buf = OwnedBuf::with_alignment::<u64>();
    let slice = buf.store_slice(&[1u8, 2, 3])?;
    let packed = buf.store(&PackedRef { a: 7, r: slice })?;
    let packed = *buf.load(packed)?;
    let r = packed.r;
    assert_eq!(buf.load(r)?, &[1, 2, 3]);
    Ok(())
}

/// An ignored zero-sized field with an alignment larger than 1 moves the
/// fields that follow it.
#[derive(Clone, Copy, ZeroCopy)]
#[repr(C)]
#[zero_copy(crate)]
struct IgnoredAligned {
    a: u8,
    #[zero_copy(ignore)]
    _z: [u64; 0],
    b: bool,
}

#[derive(Clone, Copy, ZeroCopy)]
#[repr(C, packed(2))]
#[zero_copy(crate)]
struct PackedIgnoredAligned {
    a: u8,
    #[zero_copy(ignore)]
    _z: [u64; 0],
    b: bool,
}

#[derive(Clone, Copy, ZeroCopy)]
#[repr(u8)]
#[zero_copy(crate)]
enum EnumIgnoredAligned {
    A {
        #[zero_copy(ignore)]
        _z: [u64; 0],
        b: bool,
    },
}

#[test]
fn ignored_aligned_layout() {
    assert_eq!(offset_of!(IgnoredAligned, b), 8);
    assert_eq!(size_of::<IgnoredAligned>(), 16);
    assert_eq!(offset_of!(PackedIgnoredAligned, b), 2);
    assert_eq!(size_of::<PackedIgnoredAligned>(), 4);
    assert_eq!(size_of::<EnumIgnoredAligned>(), 16);
}

#[test]
fn ignored_aligned_rejects_invalid_field() {
    let mut bytes = [0u8; 16];
    bytes[8] = 2;
    let Err(error) = load_bytes::<IgnoredAligned>(&bytes) else {
        panic!("expected an invalid bool error");
    };
    assert_eq!(error.to_string(), "Invalid bool representation 2");
}

#[test]
fn ignored_aligned_accepts_valid_field() {
    // Byte 1 is padding and must not be validated as a bool.
    let mut bytes = [0u8; 16];
    bytes[1] = 2;
    bytes[8] = 1;
    assert!(load_bytes::<IgnoredAligned>(&bytes).unwrap().b);
}

#[test]
fn ignored_aligned_pad_preserves_fields() -> Result<()> {
    let mut buf = OwnedBuf::new();
    buf.store(&IgnoredAligned {
        a: 1,
        _z: [],
        b: true,
    })?;

    assert_eq!(
        buf.as_slice(),
        &[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
    );
    Ok(())
}

#[test]
fn packed_ignored_aligned_validates_field() {
    let Err(error) = load_bytes::<PackedIgnoredAligned>(&[0, 0, 2, 0]) else {
        panic!("expected an invalid bool error");
    };
    assert_eq!(error.to_string(), "Invalid bool representation 2");

    let value = load_bytes::<PackedIgnoredAligned>(&[0, 2, 1, 0]).unwrap();
    let b = value.b;
    assert!(b);
}

#[test]
fn packed_ignored_aligned_pad_preserves_fields() -> Result<()> {
    let mut buf = OwnedBuf::new();
    buf.store(&PackedIgnoredAligned {
        a: 1,
        _z: [],
        b: true,
    })?;

    assert_eq!(buf.as_slice(), &[1, 0, 1, 0]);
    Ok(())
}

#[test]
fn enum_ignored_aligned_validates_field() {
    let mut bytes = [0u8; 16];
    bytes[8] = 2;
    assert!(load_bytes::<EnumIgnoredAligned>(&bytes).is_err());

    let mut bytes = [0u8; 16];
    bytes[1] = 2;
    bytes[8] = 1;
    let EnumIgnoredAligned::A { b, .. } = load_bytes::<EnumIgnoredAligned>(&bytes).unwrap();
    assert!(b);
}

#[test]
fn enum_ignored_aligned_pad_preserves_fields() -> Result<()> {
    let mut buf = OwnedBuf::new();
    buf.store(&EnumIgnoredAligned::A { _z: [], b: true })?;

    assert_eq!(
        buf.as_slice(),
        &[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
    );
    Ok(())
}

// `usize::MAX` is out of range for `isize`, which was previously used for the
// discriminant. It is portable, since it is the maximum on every target.
#[allow(clippy::enum_clike_unportable_variant)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, ZeroCopy)]
#[repr(usize)]
#[zero_copy(crate)]
enum EnumUsize {
    A = 1,
    B = usize::MAX,
}

#[test]
fn repr_usize_enum() -> Result<()> {
    const _: () = assert!(size_of::<EnumUsize>() == size_of::<usize>());

    let mut buf = OwnedBuf::new();
    let a = buf.store(&EnumUsize::A)?;
    let b = buf.store(&EnumUsize::B)?;
    assert_eq!(buf.load(a)?, &EnumUsize::A);
    assert_eq!(buf.load(b)?, &EnumUsize::B);

    assert!(load_bytes::<EnumUsize>(&2usize.to_ne_bytes()).is_err());
    Ok(())
}