aboutsummaryrefslogtreecommitdiff
path: root/src/derive_util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/derive_util.rs')
-rw-r--r--src/derive_util.rs127
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);
+ }
+}