1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// SPDX-License-Identifier: MIT

//! Macro to test alignment, size and offsets of structs
//!
//! This is mostly useful for creating FFI structures.
//!
//! The crucial field offset calculation was extracted from the `memoffset` crate.
//! Kudos to Gilad Naaman and Ralf Jung and all the other contributors.
//!
//! # Examples
//! ```
//! #[repr(C)]
//! struct Simple {
//!     a: u32,
//!     b: [u8; 2],
//!     c: i64,
//! }
//!
//! #[repr(C, packed)]
//! struct SimplePacked {
//!     a: u32,
//!     b: [u8; 2],
//!     c: i64,
//! }
//!
//! #[cfg(test)]
//! mod test {
//!     use testaso::testaso;
//!
//!     use super::Simple;
//!     use super::SimplePacked;
//!
//!     testaso! {
//!         struct Simple: 8, 16 => {
//!             a: 0,
//!             b: 4,
//!             c: 8
//!         }
//!
//!         struct SimplePacked: 1, 14 => {
//!             a: 0,
//!             b: 4,
//!             c: 6
//!         }
//!     }
//! }
//! ```

#![cfg_attr(not(test), no_std)]

/// Macro to test alignment, size and offsets of structs
///
/// # Examples
/// ```
/// #[repr(C)]
/// struct Simple {
///     a: u32,
///     b: [u8; 2],
///     c: i64,
/// }
///
/// #[repr(C, packed)]
/// struct SimplePacked {
///     a: u32,
///     b: [u8; 2],
///     c: i64,
/// }
///
/// #[cfg(test)]
/// mod test {
///     use testaso::testaso;
///
///     use super::Simple;
///     use super::SimplePacked;
///
///     testaso! {
///         struct Simple: 8, 16 => {
///             a: 0,
///             b: 4,
///             c: 8
///         }
///
///         struct SimplePacked: 1, 14 => {
///             a: 0,
///             b: 4,
///             c: 6
///         }
///     }
/// }
#[macro_export]
macro_rules! testaso {
    (@off $name:path=>$field:ident) => { {
        // The following was extracted from the `memoffset` crate:

        // No UB here, and the pointer does not dangle, either.
        // But we have to make sure that `uninit` lives long enough,
        // so it has to be in the same scope as `$name`. That's why
        // this declares a variable (several, actually).
        let uninit = core::mem::MaybeUninit::<$name>::uninit();
        let base_ptr: *const $name = uninit.as_ptr();

        // Make sure the field actually exists. This line ensures that a
        // compile-time error is generated if $field is accessed through a
        // Deref impl.
        #[allow(clippy::unneeded_field_pattern)]
        let $name { $field: _, .. };

        // Get the field address.
        // SAFETY:
        // Crucially, we know that this will not trigger a deref coercion because
        // of the field check we did above.
        // Also, the pointer does not dangle.
        let field_ptr = unsafe { core::ptr::addr_of!((*base_ptr).$field) };

        (field_ptr as usize) - (base_ptr as usize)
    }};

    ($(struct $name:path: $align:expr, $size:expr => { $($field:ident: $offset:expr),* })+) => {
        #[cfg(test)]
        #[test]
        fn align() {
            use core::mem::align_of;

            $(
                assert_eq!(
                    align_of::<$name>(),
                    $align,
                    "align: {}",
                    stringify!($name)
                );
            )+
        }

        #[cfg(test)]
        #[test]
        fn size() {
            use core::mem::size_of;

            $(
                assert_eq!(
                    size_of::<$name>(),
                    $size,
                    "size: {}",
                    stringify!($name)
                );
            )+
        }

        #[cfg(test)]
        #[test]
        fn offsets() {
            $(
                $(
                    assert_eq!(
                        testaso!(@off $name=>$field),
                        $offset,
                        "offset: {}::{}",
                        stringify!($name),
                        stringify!($field)
                    );
                )*
        )+
        }
    };
}

#[cfg(test)]
mod test {
    #[repr(C)]
    struct Simple {
        a: u32,
        b: [u8; 2],
        c: i64,
    }

    #[repr(C, packed)]
    struct SimplePacked {
        a: u32,
        b: [u8; 2],
        c: i64,
    }

    #[repr(C, packed(4))]
    pub struct StructPacked {
        a: u64,
        b: u64,
    }

    #[repr(C)]
    pub struct StructUnPacked {
        a: u64,
        b: u64,
    }

    mod sub {
        #[repr(C)]
        pub struct Simple {
            pub x: u32,
        }
    }

    testaso! {
        struct Simple: 8, 16 => {
            a: 0,
            b: 4,
            c: 8
        }

        struct SimplePacked: 1, 14 => {
            a: 0,
            b: 4,
            c: 6
        }

        struct sub::Simple: 4, 4 => {
            x: 0
        }

        struct StructPacked: 4, 16 => {
            a: 0,
            b: 8
        }

        struct StructUnPacked: 8, 16 => {
            a: 0,
            b: 8
        }
    }
}