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/// # Requirements
20///
21/// `T` must uphold the requirements of [`MaybeDynSized`], in particular a
22/// correct [`MaybeDynSized::BASE_SIZE`] and [`MaybeDynSized::dst_len`]
23/// implementation. These requirements ensure that the allocation made here
24/// matches the layout of `T`.
25///
26/// # Parameters
27/// - `additional_bytes_slices`: Array of byte slices that should be included
28/// without additional padding in-between. You don't need to add the bytes
29/// for [`Header`], but only additional payload.
30#[must_use]
31pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(
32 mut header: T::Header,
33 additional_bytes_slices: &[&[u8]],
34) -> Box<T> {
35 let additional_size = additional_bytes_slices
36 .iter()
37 .map(|b| b.len())
38 .sum::<usize>();
39
40 let tag_size = size_of::<T::Header>() + additional_size;
41 header.set_size(tag_size);
42
43 // Allocation size is multiple of alignment.
44 // See <https://doc.rust-lang.org/reference/type-layout.html>
45 let alloc_size = increase_to_alignment(tag_size);
46 let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap();
47 // Use a zeroed allocation so that the trailing padding in
48 // `[tag_size, alloc_size)` is initialized. The header and body writes
49 // below only cover `[0, tag_size)`; without zeroing, reading the padding
50 // through the safe `MaybeDynSized::as_bytes`/`payload` accessors would be
51 // undefined behavior. The Multiboot2 spec also mandates zero padding.
52 // SAFETY: `layout` matches the requested allocation size and alignment.
53 let heap_ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
54 assert!(!heap_ptr.is_null());
55
56 // write header
57 {
58 let len = size_of::<T::Header>();
59 let ptr = &raw const header;
60 // SAFETY: `header` is a fully initialized stack value and `heap_ptr`
61 // points into the freshly allocated destination buffer.
62 unsafe {
63 ptr::copy_nonoverlapping(ptr.cast::<u8>(), heap_ptr, len);
64 }
65 }
66
67 // write body
68 {
69 let mut write_offset = size_of::<T::Header>();
70 for &bytes in additional_bytes_slices {
71 let len = bytes.len();
72 let src = bytes.as_ptr();
73 let dst = heap_ptr.wrapping_add(write_offset);
74 // SAFETY: `src` is a valid slice and `dst` stays inside the
75 // allocated object without overlapping `src`.
76 unsafe {
77 ptr::copy_nonoverlapping(src, dst, len);
78 }
79 write_offset += len;
80 }
81 }
82
83 // This is a fat pointer for DSTs and a thin pointer for sized `T`s.
84 // SAFETY: The allocation was sized for `T` and all bytes up to the
85 // reported dynamic length were initialized above.
86 let ptr: *mut T = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(&header));
87 // SAFETY: `ptr` points to the initialized allocation described above.
88 let reference = unsafe { Box::from_raw(ptr) };
89
90 // If this panic triggers, there is a fundamental flaw in my logic. This is
91 // not the fault of an API user.
92 assert_eq!(
93 size_of_val(reference.deref()),
94 alloc_size,
95 "Allocation should match Rusts expectation"
96 );
97
98 reference
99}
100
101/// Clones a [`MaybeDynSized`] by calling [`new_boxed`].
102#[must_use]
103pub fn clone_dyn<T: MaybeDynSized<Metadata = usize> + ?Sized>(tag: &T) -> Box<T> {
104 new_boxed(tag.header().clone(), &[tag.payload()])
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::Tag;
111 use crate::test_utils::{DummyDstTag, DummyTestHeader};
112 use core::slice;
113
114 #[test]
115 fn test_new_boxed() {
116 let header = DummyTestHeader::new(DummyDstTag::ID, 0);
117 let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
118 assert_eq!(tag.header().typ(), 42);
119 assert_eq!(tag.payload(), &[0, 1, 2, 3]);
120
121 // Test that bytes are added consecutively without gaps.
122 let header = DummyTestHeader::new(0xdead_beef, 0);
123 let tag = new_boxed::<DummyDstTag>(header, &[&[0], &[1], &[2, 3]]);
124 assert_eq!(tag.header().typ(), 0xdead_beef);
125 assert_eq!(tag.payload(), &[0, 1, 2, 3]);
126 }
127
128 #[test]
129 fn test_new_boxed_zeroes_padding() {
130 // A payload of 1 byte yields a 9-byte tag in a 16-byte allocation.
131 // `as_bytes()` must exclude the 7 trailing padding bytes, while the
132 // allocation itself must be zeroed there (guaranteed by
133 // `alloc_zeroed`), as the Multiboot2 spec mandates zeroed padding
134 // between tags.
135 let header = DummyTestHeader::new(DummyDstTag::ID, 0);
136 let tag = new_boxed::<DummyDstTag>(header, &[&[0xff]]);
137 assert_eq!(tag.as_bytes().len(), 9);
138 let ptr = (&raw const *tag).cast::<u8>();
139 // SAFETY: The allocation spans `size_of_val` bytes and is fully
140 // initialized by `new_boxed` (zeroed allocation).
141 let all_bytes = unsafe { slice::from_raw_parts(ptr, size_of_val(&*tag)) };
142 assert_eq!(all_bytes.len(), 16);
143 assert_eq!(&all_bytes[9..16], &[0, 0, 0, 0, 0, 0, 0]);
144 }
145
146 #[test]
147 fn test_clone_tag() {
148 // A 5-byte payload, so that the reported tag size (13) is no
149 // multiple of the alignment.
150 let header = DummyTestHeader::new(DummyDstTag::ID, 0);
151 let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3, 4]]);
152 assert_eq!(tag.header().typ(), 42);
153 assert_eq!(tag.payload(), &[0, 1, 2, 3, 4]);
154
155 let cloned = clone_dyn(tag.as_ref());
156 // The clone must round-trip exactly; especially, the reported size
157 // must not grow to the padded allocation size.
158 assert_eq!(cloned.header(), tag.header());
159 assert_eq!(cloned.payload(), tag.payload());
160 }
161}