Skip to main content

multiboot2_common/
boxed.rs

1//! Module for [`new_boxed`].
2
3use crate::{ALIGNMENT, Header, MaybeDynSized, increase_to_alignment};
4use alloc::boxed::Box;
5use core::alloc::Layout;
6use core::ops::Deref;
7use core::ptr;
8
9/// Creates a new tag implementing [`MaybeDynSized`] on the heap.
10///
11/// This works for sized and unsized tags. However, it only makes sense to use
12/// this for tags that are DSTs (unsized). For regular sized structs, you can
13/// just create a typical constructor and box the result.
14///
15/// The provided `header`' total size (see [`Header`]) will be set dynamically
16/// by this function using [`Header::set_size`]. However, it must contain all
17/// other relevant metadata or update it in the `set_size` callback.
18///
19/// # Parameters
20/// - `additional_bytes_slices`: Array of byte slices that should be included
21///   without additional padding in-between. You don't need to add the bytes
22///   for [`Header`], but only additional payload.
23#[must_use]
24pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(
25    mut header: T::Header,
26    additional_bytes_slices: &[&[u8]],
27) -> Box<T> {
28    let additional_size = additional_bytes_slices
29        .iter()
30        .map(|b| b.len())
31        .sum::<usize>();
32
33    let tag_size = size_of::<T::Header>() + additional_size;
34    header.set_size(tag_size);
35
36    // Allocation size is multiple of alignment.
37    // See <https://doc.rust-lang.org/reference/type-layout.html>
38    let alloc_size = increase_to_alignment(tag_size);
39    let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap();
40    // SAFETY: `layout` matches the requested allocation size and alignment.
41    let heap_ptr = unsafe { alloc::alloc::alloc(layout) };
42    assert!(!heap_ptr.is_null());
43
44    // write header
45    {
46        let len = size_of::<T::Header>();
47        let ptr = &raw const header;
48        // SAFETY: `header` is a fully initialized stack value and `heap_ptr`
49        // points into the freshly allocated destination buffer.
50        unsafe {
51            ptr::copy_nonoverlapping(ptr.cast::<u8>(), heap_ptr, len);
52        }
53    }
54
55    // write body
56    {
57        let mut write_offset = size_of::<T::Header>();
58        for &bytes in additional_bytes_slices {
59            let len = bytes.len();
60            let src = bytes.as_ptr();
61            let dst = heap_ptr.wrapping_add(write_offset);
62            // SAFETY: `src` is a valid slice and `dst` stays inside the
63            // allocated object without overlapping `src`.
64            unsafe {
65                ptr::copy_nonoverlapping(src, dst, len);
66            }
67            write_offset += len;
68        }
69    }
70
71    // This is a fat pointer for DSTs and a thin pointer for sized `T`s.
72    // SAFETY: The allocation was sized for `T` and all bytes up to the
73    // reported dynamic length were initialized above.
74    let ptr: *mut T = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(&header));
75    // SAFETY: `ptr` points to the initialized allocation described above.
76    let reference = unsafe { Box::from_raw(ptr) };
77
78    // If this panic triggers, there is a fundamental flaw in my logic. This is
79    // not the fault of an API user.
80    assert_eq!(
81        size_of_val(reference.deref()),
82        alloc_size,
83        "Allocation should match Rusts expectation"
84    );
85
86    reference
87}
88
89/// Clones a [`MaybeDynSized`] by calling [`new_boxed`].
90#[must_use]
91pub fn clone_dyn<T: MaybeDynSized<Metadata = usize> + ?Sized>(tag: &T) -> Box<T> {
92    new_boxed(tag.header().clone(), &[tag.payload()])
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::Tag;
99    use crate::test_utils::{DummyDstTag, DummyTestHeader};
100
101    #[test]
102    fn test_new_boxed() {
103        let header = DummyTestHeader::new(DummyDstTag::ID, 0);
104        let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
105        assert_eq!(tag.header().typ(), 42);
106        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
107
108        // Test that bytes are added consecutively without gaps.
109        let header = DummyTestHeader::new(0xdead_beef, 0);
110        let tag = new_boxed::<DummyDstTag>(header, &[&[0], &[1], &[2, 3]]);
111        assert_eq!(tag.header().typ(), 0xdead_beef);
112        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
113    }
114
115    #[test]
116    fn test_clone_tag() {
117        let header = DummyTestHeader::new(DummyDstTag::ID, 0);
118        let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
119        assert_eq!(tag.header().typ(), 42);
120        assert_eq!(tag.payload(), &[0, 1, 2, 3]);
121
122        let _cloned = clone_dyn(tag.as_ref());
123    }
124}