Skip to main content

musli_zerocopy/buf/
padder.rs

1use core::marker::PhantomData;
2use core::mem::{MaybeUninit, align_of, size_of, size_of_val, transmute};
3use core::ptr::NonNull;
4
5use crate::buf;
6use crate::traits::{ZeroCopy, ZeroSized};
7
8/// A struct padder as provided to the [`ZeroCopy::pad`] method.
9///
10/// This knows how to find and initialize padding regions in `repr(C)` types,
11/// and provides a builder-like API to doing so.
12#[must_use = "For the writer to have an effect on `OwnedBuf` you must call `Padder::remaining` / `Padder::remaining_unsized`"]
13pub struct Padder<'a, T: ?Sized> {
14    data: NonNull<MaybeUninit<u8>>,
15    offset: usize,
16    _marker: PhantomData<&'a mut T>,
17}
18
19impl<'a, T: ?Sized> Padder<'a, T> {
20    #[inline]
21    pub(crate) fn new(data: NonNull<MaybeUninit<u8>>) -> Self {
22        Self {
23            data,
24            offset: 0,
25            _marker: PhantomData,
26        }
27    }
28
29    /// Indicate that this validate is transparent over `U`.
30    //
31    /// # Safety
32    ///
33    /// This is only allowed if `T` is `#[repr(transparent)]` over `U`.
34    #[inline]
35    pub unsafe fn transparent<U>(&mut self) -> &mut Padder<'a, U> {
36        unsafe { transmute(self) }
37    }
38
39    /// Pad around the given field with zeros.
40    ///
41    /// Note that this is necessary to do correctly in order to satisfy the
42    /// safety requirements by [`remaining()`].
43    ///
44    /// This is typically not called directly, but rather is implemented by the
45    /// [`ZeroCopy`] derive.
46    ///
47    /// [`remaining()`]: Self::remaining
48    /// [`ZeroCopy`]: derive@crate::ZeroCopy
49    ///
50    /// # Safety
51    ///
52    /// The caller must ensure that the field type `F` is an actual field in
53    /// order in the struct being padded.
54    #[inline]
55    pub unsafe fn pad<F>(&mut self)
56    where
57        F: ZeroCopy,
58    {
59        unsafe {
60            self.pad_with::<F>(align_of::<F>());
61        }
62    }
63
64    /// Pad around the given field with zeros for a struct marked
65    /// `#[repr(packed(align))]`.
66    ///
67    /// The field is aligned to `min(align, align_of::<F>())`, which is the
68    /// alignment the compiler uses for fields in a packed struct.
69    ///
70    /// Note that this is necessary to do correctly in order to satisfy the
71    /// safety requirements by [`remaining()`].
72    ///
73    /// This is typically not called directly, but rather is implemented by the
74    /// [`ZeroCopy`] derive.
75    ///
76    /// [`remaining()`]: Self::remaining
77    /// [`ZeroCopy`]: derive@crate::ZeroCopy
78    ///
79    /// # Safety
80    ///
81    /// The caller must ensure that the field type `F` is an actual field in
82    /// order in the struct being padded and that `align` matches the argument
83    /// provided to `#[repr(packed)]` (note that empty means 1). `align` must
84    /// be a power of two.
85    #[inline]
86    pub unsafe fn pad_with<F>(&mut self, align: usize)
87    where
88        F: ZeroCopy,
89    {
90        unsafe {
91            let align = align.min(align_of::<F>());
92            let count = buf::padding_to(self.offset, align);
93            // zero out padding.
94            self.data.as_ptr().add(self.offset).write_bytes(0, count);
95            self.offset += count;
96
97            if F::PADDED {
98                let ptr = NonNull::new_unchecked(self.data.as_ptr().add(self.offset));
99                let mut padder = Padder::new(ptr);
100                F::pad(&mut padder);
101                padder.remaining();
102            }
103
104            self.offset += size_of::<F>();
105        }
106    }
107
108    /// Pad up to an ignored zero-sized field `F`.
109    ///
110    /// Zero-sized fields can have an alignment larger than 1, in which case
111    /// they affect the offset of the fields that follow them.
112    ///
113    /// This is typically not called directly, but rather is implemented by the
114    /// [`ZeroCopy`] derive.
115    ///
116    /// [`ZeroCopy`]: derive@crate::ZeroCopy
117    ///
118    /// # Safety
119    ///
120    /// The caller must ensure that the field type `F` is an actual field in
121    /// order in the struct being padded.
122    #[inline]
123    pub unsafe fn pad_zero_sized<F>(&mut self)
124    where
125        F: ZeroSized,
126    {
127        unsafe {
128            self.pad_zero_sized_with::<F>(align_of::<F>());
129        }
130    }
131
132    /// Pad up to an ignored zero-sized field `F` in a struct marked
133    /// `#[repr(packed(align))]`.
134    ///
135    /// The field is aligned to `min(align, align_of::<F>())`.
136    ///
137    /// This is typically not called directly, but rather is implemented by the
138    /// [`ZeroCopy`] derive.
139    ///
140    /// [`ZeroCopy`]: derive@crate::ZeroCopy
141    ///
142    /// # Safety
143    ///
144    /// The caller must ensure that the field type `F` is an actual field in
145    /// order in the struct being padded and that `align` matches the argument
146    /// provided to `#[repr(packed)]` (note that empty means 1). `align` must
147    /// be a power of two.
148    #[inline]
149    pub unsafe fn pad_zero_sized_with<F>(&mut self, align: usize)
150    where
151        F: ZeroSized,
152    {
153        unsafe {
154            let count = buf::padding_to(self.offset, align.min(align_of::<F>()));
155            // zero out padding.
156            self.data.as_ptr().add(self.offset).write_bytes(0, count);
157            self.offset += count;
158        }
159    }
160
161    /// Specific method to both pad for a discriminant and load it
162    /// simultaneously for inspection.
163    ///
164    /// # Safety
165    ///
166    /// The caller must ensure that the field type `D` is the actual type of the
167    /// discriminant first in order in the enum being padded and that `D` is a
168    /// primitive that does not contain any interior padding.
169    #[inline]
170    pub unsafe fn pad_discriminant<D>(&mut self) -> D
171    where
172        D: ZeroCopy,
173    {
174        unsafe {
175            let count = buf::padding_to(self.offset, align_of::<D>());
176            // zero out padding.
177            self.data.as_ptr().add(self.offset).write_bytes(0, count);
178            let at = self.offset + count;
179            let value = self.data.as_ptr().add(at).cast::<D>().read_unaligned();
180            self.offset = at + size_of::<D>();
181            value
182        }
183    }
184
185    /// Finish writing the current buffer.
186    ///
187    /// This is typically not called directly, but rather is implemented by the
188    /// [`ZeroCopy`] derive.
189    ///
190    /// [`ZeroCopy`]: derive@crate::ZeroCopy
191    ///
192    /// # Safety
193    ///
194    /// Before calling `remaining()`, the caller must ensure that they've called
195    /// [`pad::<F>()`] *in order* for every field in a struct being serialized
196    /// where `F` is the type of the field. Otherwise we might not have written
197    /// the necessary padding to ensure that all bytes related to the struct are
198    /// initialized. Failure to do so would result in undefined behavior.
199    ///
200    /// Fields which are [`ZeroSized`] must be padded with
201    /// [`pad_zero_sized::<F>()`] unless their alignment is 1, since they can
202    /// affect the offset of the fields that follow them.
203    ///
204    /// [`pad::<F>()`]: Self::pad
205    /// [`pad_zero_sized::<F>()`]: Self::pad_zero_sized
206    /// [`ZeroSized`]: crate::traits::ZeroSized
207    #[inline]
208    pub unsafe fn remaining(self)
209    where
210        T: Sized,
211    {
212        debug_assert!(
213            self.offset <= size_of::<T>(),
214            "padded fields extend past the end of the type"
215        );
216
217        unsafe {
218            let count = size_of::<T>() - self.offset;
219            self.data.as_ptr().add(self.offset).write_bytes(0, count);
220        }
221    }
222
223    /// Finalize remaining padding based on the size of an unsized value.
224    #[inline]
225    pub(crate) unsafe fn remaining_unsized(self, value: &T) {
226        debug_assert!(
227            self.offset <= size_of_val(value),
228            "padded fields extend past the end of the value"
229        );
230
231        unsafe {
232            let count = size_of_val(value) - self.offset;
233            self.data.as_ptr().add(self.offset).write_bytes(0, count);
234        }
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use std::mem::size_of;
241
242    use anyhow::Result;
243
244    use crate::{OwnedBuf, ZeroCopy, buf};
245
246    #[test]
247    fn ensure_padding() -> Result<()> {
248        #[derive(Debug, PartialEq, Eq, ZeroCopy)]
249        #[repr(C)]
250        #[zero_copy(crate)]
251        struct ZeroPadded(u8, u16, u64);
252
253        let padded = ZeroPadded(
254            0x01u8.to_be(),
255            0x0203u16.to_be(),
256            0x0405060708090a0bu64.to_be(),
257        );
258
259        let mut buf = OwnedBuf::new();
260
261        // Note: You're responsible for ensuring that the buffer has enough
262        // capacity.
263        buf.reserve(size_of::<ZeroPadded>())?;
264
265        // SAFETY: We do not pad beyond known fields and are making sure to
266        // initialize all of the buffer.
267        unsafe {
268            buf::store_unaligned(buf.as_nonnull(), &padded);
269            buf.advance(size_of::<ZeroPadded>());
270        }
271
272        // Note: The bytes are explicitly convert to big-endian encoding above.
273        assert_eq!(
274            buf.as_slice(),
275            &[1, 0, 2, 3, 0, 0, 0, 0, 4, 5, 6, 7, 8, 9, 10, 11]
276        );
277
278        Ok(())
279    }
280}