fixed_slice_vec/
single.rs

1//! Functions relating to managing references to well-typed Rust values
2//! stored inside arbitrary byte slices.
3use core::mem::*;
4
5/// Plenty can go wrong when attempting to embed a value in arbitrary bytes
6#[derive(Debug, PartialEq)]
7pub enum EmbedValueError<E> {
8    /// Difficulty generating the necessary mutable reference
9    /// to the embedded location.
10    SplitUninitError(SplitUninitError),
11    /// Initializing the value went wrong somehow.
12    ConstructionError(E),
13}
14
15impl<E> From<SplitUninitError> for EmbedValueError<E> {
16    #[inline]
17    fn from(e: SplitUninitError) -> Self {
18        EmbedValueError::SplitUninitError(e)
19    }
20}
21
22/// Initialize a value into location within a provided byte slice,
23/// and return a mutable reference to that value.
24///
25/// The user-provided constructor function also has access to the
26/// portions of the byte slice after the region allocated for
27/// the embedded value itself.
28///
29/// # Safety
30///
31/// Panics in debug mode if destination slice's underlying
32/// pointer has somehow been contrived to be null.
33///
34/// This function does nothing to ensure that the embedded value will be
35/// dropped when the returned reference is dropped. The caller is
36/// responsible for cleaning up any side effects of the embedded value
37/// outside of the destination slice.
38///
39/// If the item type `T` contains any padding bytes
40/// then those padding bytes may be observable in the provided slice
41/// after the reference is dropped. Observing padding bytes is
42/// undefined behavior.
43#[inline]
44pub unsafe fn embed<'a, T, F, E>(
45    destination: &'a mut [u8],
46    f: F,
47) -> Result<&'a mut T, EmbedValueError<E>>
48where
49    F: FnOnce(&'a mut [u8]) -> Result<T, E>,
50{
51    debug_assert!(!destination.as_ptr().is_null());
52    let (_prefix, uninit_ref, suffix) = split_uninit_from_bytes(destination)?;
53    let ptr = uninit_ref.as_mut_ptr();
54    core::ptr::write(ptr, f(suffix).map_err(EmbedValueError::ConstructionError)?);
55    // We literally just initialized the value, so it's safe to call it init
56    if let Some(ptr) = ptr.as_mut() {
57        Ok(ptr)
58    } else {
59        unreachable!("Just initialized the value and the pointer is based on a non-null slice")
60    }
61}
62
63/// Initialize a value into location within a provided byte slice,
64/// and return a mutable reference to that value.
65///
66/// The user-provided constructor function also has access to the
67/// portions of the byte slice after the region allocated for
68/// the embedded value itself.
69///
70/// # Safety
71///
72/// Panics in debug mode if destination slice's underlying
73/// pointer has somehow been contrived to be null.
74///
75/// This function does nothing to ensure that the embedded value will be
76/// dropped when the returned reference is dropped. The caller is
77/// responsible for cleaning up any side effects of the embedded value
78/// outside of the destination slice.
79#[inline]
80pub fn embed_uninit<'a, T, F, E>(
81    destination: &'a mut [MaybeUninit<u8>],
82    f: F,
83) -> Result<&'a mut T, EmbedValueError<E>>
84where
85    F: FnOnce(&'a mut [MaybeUninit<u8>]) -> Result<T, E>,
86{
87    debug_assert!(!destination.as_ptr().is_null());
88    let (_prefix, uninit_ref, suffix) = split_uninit_from_uninit_bytes(destination)?;
89    unsafe {
90        let ptr = uninit_ref.as_mut_ptr();
91        core::ptr::write(ptr, f(suffix).map_err(EmbedValueError::ConstructionError)?);
92        // We literally just initialized the value, so it's safe to call it init
93        if let Some(ptr) = ptr.as_mut() {
94            Ok(ptr)
95        } else {
96            unreachable!("Just initialized the value and the pointer is based on a non-null slice")
97        }
98    }
99}
100
101/// Plenty can go wrong when attempting to find space for a value in arbitrary bytes.
102#[derive(Debug, PartialEq)]
103pub enum SplitUninitError {
104    /// Zero sized types shouldn't be placed anywhere into a byte slice anyhow.
105    ZeroSizedTypesUnsupported,
106    /// Could not calculate a valid alignment offset from the given the
107    /// starting point which would result in a properly-aligned value.
108    Unalignable,
109    /// Could not theoretically fit the target value into the provided byte slice
110    /// due to a combination of the type's alignment and size.
111    InsufficientSpace,
112}
113
114/// Split out a mutable reference to an uninitialized struct at an available
115/// location within a provided slice of bytes.
116///
117/// Does not access or mutate the content of the provided `destination` byte
118/// slice.
119#[allow(clippy::type_complexity)]
120#[inline]
121pub fn split_uninit_from_bytes<T>(
122    destination: &mut [u8],
123) -> Result<(&mut [u8], &mut MaybeUninit<T>, &mut [u8]), SplitUninitError> {
124    debug_assert!(!destination.as_ptr().is_null());
125    // Here we rely on the assurance that MaybeUninit has the same layout
126    // as its parameterized type, and our knowledge of the implementation
127    // of `split_uninit_from_uninit_bytes`, namely that it never accesses
128    // or mutates any content passed to it.
129    let uninit_bytes = unsafe { &mut *(destination as *mut [u8] as *mut [MaybeUninit<u8>]) };
130    let (prefix, uninit_ref, suffix): (_, &mut MaybeUninit<T>, _) =
131        split_uninit_from_uninit_bytes(uninit_bytes)?;
132    let uninit_prefix = unsafe { &mut *(prefix as *mut [MaybeUninit<u8>] as *mut [u8]) };
133    let uninit_ref = unsafe { transmute(uninit_ref) };
134    let uninit_suffix = unsafe { &mut *(suffix as *mut [MaybeUninit<u8>] as *mut [u8]) };
135    Ok((uninit_prefix, uninit_ref, uninit_suffix))
136}
137
138/// Split out a mutable reference to an uninitialized struct at an available
139/// location within a provided slice of maybe-uninitialized bytes.
140///
141/// Does not access or mutate the content of the provided `destination` byte
142/// slice.
143#[allow(clippy::type_complexity)]
144#[inline]
145pub fn split_uninit_from_uninit_bytes<T>(
146    destination: &mut [MaybeUninit<u8>],
147) -> Result<
148    (
149        &mut [MaybeUninit<u8>],
150        &mut MaybeUninit<T>,
151        &mut [MaybeUninit<u8>],
152    ),
153    SplitUninitError,
154> {
155    debug_assert!(!destination.as_ptr().is_null());
156    if size_of::<T>() == 0 {
157        return Err(SplitUninitError::ZeroSizedTypesUnsupported);
158    }
159    let ptr = destination.as_mut_ptr();
160    let offset = ptr.align_offset(align_of::<T>());
161    if offset == core::usize::MAX {
162        return Err(SplitUninitError::Unalignable);
163    }
164    if offset > destination.len() {
165        return Err(SplitUninitError::InsufficientSpace);
166    }
167    if let Some(end) = offset.checked_add(size_of::<T>()) {
168        if end > destination.len() {
169            return Err(SplitUninitError::InsufficientSpace);
170        }
171    } else {
172        return Err(SplitUninitError::InsufficientSpace);
173    }
174    let (prefix, rest) = destination.split_at_mut(offset);
175    let (middle, suffix) = rest.split_at_mut(size_of::<T>());
176    let maybe_uninit = middle.as_mut_ptr() as *mut MaybeUninit<T>;
177    let maybe_uninit = if let Some(maybe_uninit) = unsafe { maybe_uninit.as_mut() } {
178        maybe_uninit
179    } else {
180        unreachable!("Should be non-null since we rely on the input byte slice being non-null.")
181    };
182    Ok((prefix, maybe_uninit, suffix))
183}
184
185#[cfg(test)]
186#[allow(dead_code)]
187mod tests {
188    use super::*;
189    #[derive(PartialEq)]
190    struct ZST;
191
192    #[derive(Default)]
193    struct TooBig {
194        colossal: [Colossal; 32],
195    }
196    #[derive(Default)]
197    struct Colossal {
198        huge: [Huge; 32],
199    }
200    #[derive(Default)]
201    struct Huge {
202        large: [Large; 32],
203    }
204    #[derive(Default)]
205    struct Large {
206        medium: [u64; 32],
207    }
208
209    #[test]
210    fn zero_sized_types_not_permitted() {
211        let mut bytes = [0u8; 64];
212        if let Err(e) = split_uninit_from_bytes::<ZST>(&mut bytes[..]) {
213            assert_eq!(SplitUninitError::ZeroSizedTypesUnsupported, e);
214        } else {
215            unreachable!("Expected an err");
216        }
217        if let Err(e) = unsafe { embed(&mut bytes[..], |_| -> Result<ZST, ()> { Ok(ZST) }) } {
218            assert_eq!(
219                EmbedValueError::SplitUninitError(SplitUninitError::ZeroSizedTypesUnsupported),
220                e
221            );
222        } else {
223            unreachable!("Expected an err");
224        }
225
226        let mut uninit_bytes: [MaybeUninit<u8>; 64] =
227            unsafe { MaybeUninit::uninit().assume_init() };
228        if let Err(e) = split_uninit_from_uninit_bytes::<ZST>(&mut uninit_bytes[..]) {
229            assert_eq!(SplitUninitError::ZeroSizedTypesUnsupported, e);
230        } else {
231            unreachable!("Expected an err");
232        }
233        if let Err(e) = embed_uninit(&mut uninit_bytes[..], |_| -> Result<ZST, ()> { Ok(ZST) }) {
234            assert_eq!(
235                EmbedValueError::SplitUninitError(SplitUninitError::ZeroSizedTypesUnsupported),
236                e
237            );
238        } else {
239            unreachable!("Expected an err");
240        }
241    }
242
243    #[test]
244    fn split_not_enough_space_detected() {
245        let mut bytes = [0u8; 64];
246        if let Err(e) = split_uninit_from_bytes::<TooBig>(&mut bytes[..]) {
247            match e {
248                SplitUninitError::InsufficientSpace | SplitUninitError::Unalignable => (),
249                _ => unreachable!("Unexpected error kind"),
250            }
251        } else {
252            unreachable!("Expected an err");
253        }
254    }
255
256    #[test]
257    fn split_uninit_not_enough_space_detected() {
258        let mut uninit_bytes: [MaybeUninit<u8>; 64] =
259            unsafe { MaybeUninit::uninit().assume_init() };
260        if let Err(e) = split_uninit_from_uninit_bytes::<TooBig>(&mut uninit_bytes[..]) {
261            match e {
262                SplitUninitError::InsufficientSpace | SplitUninitError::Unalignable => (),
263                _ => unreachable!("Unexpected error kind"),
264            }
265        } else {
266            unreachable!("Expected an err");
267        }
268    }
269
270    #[test]
271    fn split_uninit_from_bytes_observe_leftovers() {
272        let mut bytes = [0u8; 61];
273        match split_uninit_from_bytes::<[u16; 3]>(&mut bytes[..]) {
274            Ok((prefix, mid, suffix)) => {
275                *mid = MaybeUninit::new([3, 4, 5]);
276                for v in prefix {
277                    assert_eq!(0, *v);
278                }
279                for v in suffix {
280                    assert_eq!(0, *v);
281                }
282            }
283            Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability
284            Err(e) => unreachable!("Unexpected error: {:?}", e),
285        }
286    }
287
288    #[test]
289    fn split_uninit_from_uninit_bytes_observe_leftovers() {
290        let mut bytes: [MaybeUninit<u8>; 64] = unsafe { MaybeUninit::uninit().assume_init() };
291        match split_uninit_from_uninit_bytes::<[u16; 3]>(&mut bytes[..]) {
292            Ok((prefix, mid, suffix)) => {
293                *mid = MaybeUninit::new([3, 4, 5]);
294                let had_prefix = prefix.len() > 0;
295                let had_suffix = suffix.len() > 0;
296                assert!(had_prefix | had_suffix);
297            }
298            Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability
299            Err(e) => unreachable!("Unexpected error: {:?}", e),
300        }
301    }
302
303    #[test]
304    fn split_uninit_from_bytes_empty() {
305        let bytes: &mut [u8] = &mut [];
306        assert_eq!(
307            SplitUninitError::InsufficientSpace,
308            split_uninit_from_bytes::<[u16; 3]>(bytes).unwrap_err()
309        );
310    }
311
312    #[test]
313    fn split_uninit_from_uninit_bytes_empty() {
314        let bytes: &mut [MaybeUninit<u8>] = &mut [];
315        assert_eq!(
316            SplitUninitError::InsufficientSpace,
317            split_uninit_from_uninit_bytes::<[u16; 3]>(bytes).unwrap_err()
318        );
319    }
320
321    #[test]
322    fn embed_not_enough_space_detected() {
323        let mut bytes = [0u8; 64];
324        if let Err(e) = unsafe {
325            embed(&mut bytes[..], |_| -> Result<Colossal, ()> {
326                unreachable!("Don't expect this to execute since we can tell from the types that there is not enough space")
327            })
328        } {
329            match e {
330                EmbedValueError::SplitUninitError(SplitUninitError::InsufficientSpace)
331                | EmbedValueError::SplitUninitError(SplitUninitError::Unalignable) => (),
332                _ => unreachable!("Unexpected error kind"),
333            }
334        } else {
335            unreachable!("Expected an err");
336        }
337    }
338
339    #[test]
340    fn embed_uninit_not_enough_space_detected() {
341        let mut uninit_bytes: [MaybeUninit<u8>; 64] =
342            unsafe { MaybeUninit::uninit().assume_init() };
343        if let Err(e) = embed_uninit(&mut uninit_bytes[..], |_| -> Result<Colossal, ()> {
344            unreachable!("Don't expect this to execute since we can tell from the types that there is not enough space")
345        }) {
346            match e {
347                EmbedValueError::SplitUninitError(SplitUninitError::InsufficientSpace)
348                | EmbedValueError::SplitUninitError(SplitUninitError::Unalignable) => (),
349                _ => unreachable!("Unexpected error kind"),
350            }
351        } else {
352            unreachable!("Expected an err");
353        }
354    }
355
356    #[test]
357    fn happy_path_split() {
358        let mut bytes = [0u8; 512];
359        let (prefix, _large_ref, suffix) = match split_uninit_from_bytes::<Large>(&mut bytes[..]) {
360            Ok(r) => r,
361            Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability
362            Err(e) => unreachable!("Unexpected error: {:?}", e),
363        };
364        assert_eq!(
365            prefix.len() + core::mem::size_of::<Large>() + suffix.len(),
366            bytes.len()
367        );
368    }
369
370    #[test]
371    fn happy_path_split_uninit() {
372        let mut uninit_bytes: [MaybeUninit<u8>; 512] =
373            unsafe { MaybeUninit::uninit().assume_init() };
374        let (prefix, _large_ref, suffix) =
375            match split_uninit_from_uninit_bytes::<Large>(&mut uninit_bytes[..]) {
376                Ok(r) => r,
377                Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability
378                Err(e) => unreachable!("Unexpected error: {:?}", e),
379            };
380        assert_eq!(
381            prefix.len() + core::mem::size_of::<Large>() + suffix.len(),
382            uninit_bytes.len()
383        );
384    }
385
386    #[test]
387    fn happy_path_embed() {
388        const BACKING_BYTES_MAX_SIZE: usize = 512;
389        let mut bytes = [2u8; BACKING_BYTES_MAX_SIZE];
390        let large_ref = match unsafe {
391            embed(&mut bytes[..], |b| -> Result<Large, ()> {
392                assert!(b.iter().all(|b| *b == 2));
393                let mut l = Large::default();
394                l.medium[0] = 3;
395                l.medium[1] = 1;
396                l.medium[2] = 4;
397                Ok(l)
398            })
399        } {
400            Ok(r) => r,
401            Err(EmbedValueError::SplitUninitError(SplitUninitError::Unalignable)) => return (), // Most likely MIRI messing with align-ability
402            Err(e) => unreachable!("Unexpected error: {:?}", e),
403        };
404
405        assert_eq!(3, large_ref.medium[0]);
406        assert_eq!(1, large_ref.medium[1]);
407        assert_eq!(4, large_ref.medium[2]);
408    }
409    #[test]
410    fn happy_path_embed_uninit() {
411        const BACKING_BYTES_MAX_SIZE: usize = 512;
412        let mut uninit_bytes: [MaybeUninit<u8>; BACKING_BYTES_MAX_SIZE] =
413            unsafe { MaybeUninit::uninit().assume_init() };
414        let large_ref = match embed_uninit(&mut uninit_bytes[..], |_| -> Result<Large, ()> {
415            let mut l = Large::default();
416            l.medium[0] = 3;
417            l.medium[1] = 1;
418            l.medium[2] = 4;
419            Ok(l)
420        }) {
421            Ok(r) => r,
422            Err(EmbedValueError::SplitUninitError(SplitUninitError::Unalignable)) => return (), // Most likely MIRI messing with align-ability
423            Err(e) => unreachable!("Unexpected error: {:?}", e),
424        };
425        assert_eq!(3, large_ref.medium[0]);
426        assert_eq!(1, large_ref.medium[1]);
427        assert_eq!(4, large_ref.medium[2]);
428    }
429    #[test]
430    fn embed_does_not_run_drops() {
431        let mut storage: [u8; 16] = [0u8; 16];
432        #[derive(Debug)]
433        struct Target(bool);
434        impl Drop for Target {
435            fn drop(&mut self) {
436                self.0 = true;
437            }
438        }
439        let emb = unsafe {
440            embed(&mut storage[..], move |_leftovers| {
441                Result::<Target, ()>::Ok(Target(false))
442            })
443            .unwrap()
444        };
445
446        assert!(!emb.0);
447    }
448    #[test]
449    fn embed_uninit_does_not_run_drops() {
450        let mut storage: [MaybeUninit<u8>; 16] = unsafe { MaybeUninit::uninit().assume_init() };
451        #[derive(Debug)]
452        struct Target(bool);
453        impl Drop for Target {
454            fn drop(&mut self) {
455                self.0 = true;
456            }
457        }
458        let emb = embed_uninit(&mut storage[..], move |_leftovers| {
459            Result::<Target, ()>::Ok(Target(false))
460        })
461        .unwrap();
462
463        assert!(!emb.0);
464    }
465}