diff options
Diffstat (limited to 'src/derive_util.rs')
-rw-r--r-- | src/derive_util.rs | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/src/derive_util.rs b/src/derive_util.rs new file mode 100644 index 0000000..edf88e3 --- /dev/null +++ b/src/derive_util.rs @@ -0,0 +1,127 @@ +// Copyright 2022 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! Utilities used by `zerocopy-derive`. +//! +//! These are defined in `zerocopy` rather than in code generated by +//! `zerocopy-derive` so that they can be compiled once rather than recompiled +//! for every pair of type and trait (in other words, if they were defined in +//! generated code, then deriving `AsBytes` and `FromBytes` on three different +//! types would result in the code in question being emitted and compiled six +//! different times). + +#![allow(missing_debug_implementations)] + +use core::marker::PhantomData; + +/// A compile-time check that should be one particular value. +pub trait ShouldBe<const VALUE: bool> {} + +/// A struct for checking whether `T` contains padding. +pub struct HasPadding<T: ?Sized, const VALUE: bool>(PhantomData<T>); + +impl<T: ?Sized, const VALUE: bool> ShouldBe<VALUE> for HasPadding<T, VALUE> {} + +/// Does the struct type `$t` have padding? +/// +/// `$ts` is the list of the type of every field in `$t`. `$t` must be a +/// struct type, or else `struct_has_padding!`'s result may be meaningless. +/// +/// Note that `struct_has_padding!`'s results are independent of `repr` since +/// they only consider the size of the type and the sizes of the fields. +/// Whatever the repr, the size of the type already takes into account any +/// padding that the compiler has decided to add. Structs with well-defined +/// representations (such as `repr(C)`) can use this macro to check for padding. +/// Note that while this may yield some consistent value for some `repr(Rust)` +/// structs, it is not guaranteed across platforms or compilations. +#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`. +#[macro_export] +macro_rules! struct_has_padding { + ($t:ty, $($ts:ty),*) => { + core::mem::size_of::<$t>() > 0 $(+ core::mem::size_of::<$ts>())* + }; +} + +/// Does the union type `$t` have padding? +/// +/// `$ts` is the list of the type of every field in `$t`. `$t` must be a +/// union type, or else `union_has_padding!`'s result may be meaningless. +/// +/// Note that `union_has_padding!`'s results are independent of `repr` since +/// they only consider the size of the type and the sizes of the fields. +/// Whatever the repr, the size of the type already takes into account any +/// padding that the compiler has decided to add. Unions with well-defined +/// representations (such as `repr(C)`) can use this macro to check for padding. +/// Note that while this may yield some consistent value for some `repr(Rust)` +/// unions, it is not guaranteed across platforms or compilations. +#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`. +#[macro_export] +macro_rules! union_has_padding { + ($t:ty, $($ts:ty),*) => { + false $(|| core::mem::size_of::<$t>() != core::mem::size_of::<$ts>())* + }; +} + +#[cfg(test)] +mod tests { + use crate::util::testutil::*; + + #[test] + fn test_struct_has_padding() { + // Test that, for each provided repr, `struct_has_padding!` reports the + // expected value. + macro_rules! test { + (#[$cfg:meta] ($($ts:ty),*) => $expect:expr) => {{ + #[$cfg] + struct Test($($ts),*); + assert_eq!(struct_has_padding!(Test, $($ts),*), $expect); + }}; + (#[$cfg:meta] $(#[$cfgs:meta])* ($($ts:ty),*) => $expect:expr) => { + test!(#[$cfg] ($($ts),*) => $expect); + test!($(#[$cfgs])* ($($ts),*) => $expect); + }; + } + + test!(#[repr(C)] #[repr(transparent)] #[repr(packed)] () => false); + test!(#[repr(C)] #[repr(transparent)] #[repr(packed)] (u8) => false); + test!(#[repr(C)] #[repr(transparent)] #[repr(packed)] (u8, ()) => false); + test!(#[repr(C)] #[repr(packed)] (u8, u8) => false); + + test!(#[repr(C)] (u8, AU64) => true); + // Rust won't let you put `#[repr(packed)]` on a type which contains a + // `#[repr(align(n > 1))]` type (`AU64`), so we have to use `u64` here. + // It's not ideal, but it definitely has align > 1 on /some/ of our CI + // targets, and this isn't a particularly complex macro we're testing + // anyway. + test!(#[repr(packed)] (u8, u64) => false); + } + + #[test] + fn test_union_has_padding() { + // Test that, for each provided repr, `union_has_padding!` reports the + // expected value. + macro_rules! test { + (#[$cfg:meta] {$($fs:ident: $ts:ty),*} => $expect:expr) => {{ + #[$cfg] + #[allow(unused)] // fields are never read + union Test{ $($fs: $ts),* } + assert_eq!(union_has_padding!(Test, $($ts),*), $expect); + }}; + (#[$cfg:meta] $(#[$cfgs:meta])* {$($fs:ident: $ts:ty),*} => $expect:expr) => { + test!(#[$cfg] {$($fs: $ts),*} => $expect); + test!($(#[$cfgs])* {$($fs: $ts),*} => $expect); + }; + } + + test!(#[repr(C)] #[repr(packed)] {a: u8} => false); + test!(#[repr(C)] #[repr(packed)] {a: u8, b: u8} => false); + + // Rust won't let you put `#[repr(packed)]` on a type which contains a + // `#[repr(align(n > 1))]` type (`AU64`), so we have to use `u64` here. + // It's not ideal, but it definitely has align > 1 on /some/ of our CI + // targets, and this isn't a particularly complex macro we're testing + // anyway. + test!(#[repr(C)] #[repr(packed)] {a: u8, b: u64} => true); + } +} |