rootcause_internals/report/data.rs
1//! This module encapsulates the fields of the [`ReportData`]. Since this is the only place
2//! they are visible, this means that the type of the [`ReportVtable`] is guaranteed to always be in sync
3//! with the type of the actual context. This follows from the fact that they are in sync
4//! when created and that the API offers no way to change the [`ReportVtable`] or context type after
5//! creation.
6
7use core::ptr::NonNull;
8
9use alloc::vec::Vec;
10
11use crate::{
12 attachment::RawAttachment,
13 handlers::ContextHandler,
14 report::{
15 raw::{RawReport, RawReportMut, RawReportRef},
16 vtable::ReportVtable,
17 },
18 util::Erased,
19};
20
21/// Type-erased report data structure with vtable-based dispatch.
22///
23/// This struct uses `#[repr(C)]` to enable safe field access in type-erased contexts,
24/// allowing access to the vtable and other fields even when the concrete context type `C` is unknown.
25#[repr(C)]
26pub(super) struct ReportData<C: 'static> {
27 vtable: &'static ReportVtable,
28 children: Vec<RawReport>,
29 attachments: Vec<RawAttachment>,
30 context: C,
31}
32
33impl<C: 'static> ReportData<C> {
34 /// Creates a new [`ReportData`] with the specified handler, context, children and attachments.
35 ///
36 /// This method creates the vtable for type-erased dispatch and pairs it with the report data.
37 pub(super) fn new<H: ContextHandler<C>>(
38 context: C,
39 children: Vec<RawReport>,
40 attachments: Vec<RawAttachment>,
41 ) -> Self {
42 Self {
43 vtable: ReportVtable::new::<C, H>(),
44 children,
45 attachments,
46 context,
47 }
48 }
49}
50
51impl RawReport {
52 /// # Safety
53 ///
54 /// - The caller must ensure that the type `C` matches the actual context type stored in the [`ReportData`].
55 /// - The caller must ensure that this is the only existing reference pointing to
56 /// the inner [`ReportData`].
57 pub unsafe fn into_parts<C: 'static>(self) -> (C, Vec<RawReport>, Vec<RawAttachment>) {
58 let ptr: NonNull<ReportData<Erased>> = self.into_non_null();
59 let ptr: NonNull<ReportData<C>> = ptr.cast::<ReportData<C>>();
60 let arc: triomphe::Arc<ReportData<C>> = unsafe { triomphe::Arc::from_raw(ptr.as_ptr()) };
61 match triomphe::Arc::try_unique(arc) {
62 Ok(unique) => {
63 let data = triomphe::UniqueArc::into_inner(unique);
64 (data.context, data.children, data.attachments)
65 }
66 Err(_) => {
67 if cfg!(debug_assertions) {
68 unreachable!("Caller did not fulfill the guarantee that pointer is unique")
69 } else {
70 unsafe { core::hint::unreachable_unchecked() }
71 }
72 }
73 }
74 }
75}
76
77impl<'a> RawReportRef<'a> {
78 /// Returns a reference to the [`ReportVtable`] of the [`ReportData`] instance.
79 pub(super) fn vtable(self) -> &'static ReportVtable {
80 let ptr = self.as_ptr();
81 // SAFETY: We don't know the actual inner context type, but we do know
82 // that it points to an instance of `ReportData<C>` for some specific `C`.
83 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
84 // the fields before the actual context.
85 //
86 // We need to take care to avoid creating an actual reference to
87 // the `ReportData` itself though, as that would still be undefined behavior
88 // since we don't have the right type.
89 unsafe {
90 let vtable_ptr = &raw const (*ptr).vtable;
91 *vtable_ptr
92 }
93 }
94
95 /// Returns the child reports of this report.
96 pub fn children(self) -> &'a [RawReport] {
97 let ptr = self.as_ptr();
98 // SAFETY: We don't know the actual inner context type, but we do know
99 // that it points to an instance of `ReportData<C>` for some specific `C`.
100 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
101 // the fields before the actual context.
102 //
103 // We need to take care to avoid creating an actual reference to
104 // the `ReportData` itself though, as that would still be undefined behavior
105 // since we don't have the right type.
106 //
107 // We return a pointer with lifetime `'a`, which we can do because the `RawReportRef<'a>`
108 // guarantees that the data is valid to read for at least that long.
109 unsafe {
110 let children_ptr = &raw const (*ptr).children;
111 &*children_ptr
112 }
113 }
114
115 /// Returns the attachments of this report.
116 pub fn attachments(self) -> &'a [RawAttachment] {
117 let ptr = self.as_ptr();
118 // SAFETY: We don't know the actual inner context type, but we do know
119 // that it points to an instance of `ReportData<C>` for some specific `C`.
120 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
121 // the fields before the actual context.
122 //
123 // We need to take care to avoid creating an actual reference to
124 // the `ReportData` itself though, as that would still be undefined behavior
125 // since we don't have the right type.
126 //
127 // We return a pointer with lifetime `'a`, which we can do because the `RawReportRef<'a>`
128 // guarantees that the data is valid to read for at least that long.
129 unsafe {
130 let attachments_ptr = &raw const (*ptr).attachments;
131 &*attachments_ptr
132 }
133 }
134
135 /// Accesses the inner context of the [`ReportData`] instance as a reference to the specified type.
136 ///
137 /// # Safety
138 ///
139 /// The caller must ensure that the type `C` matches the actual context type stored in the [`ReportData`].
140 pub unsafe fn context_downcast_unchecked<C: 'static>(self) -> &'a C {
141 // SAFETY: The inner function requires that `C` matches the type stored, but that is guaranteed by our caller.
142 let this = unsafe { self.cast_inner::<C>() };
143 &this.context
144 }
145}
146
147impl<'a> RawReportMut<'a> {
148 /// Gets a mutable reference to the child reports.
149 pub fn children_mut(self) -> &'a mut Vec<RawReport> {
150 let ptr = self.into_mut_ptr();
151
152 // SAFETY: We don't know the actual inner context type, but we do know
153 // that it points to an instance of `ReportData<C>` for some specific `C`.
154 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
155 // the fields before the actual context.
156 //
157 // We need to take care to avoid creating an actual reference to
158 // the `ReportData` itself though, as that would still be undefined behavior
159 // since we don't have the right type.
160 //
161 // We return a mutable pointer with lifetime `'a`, which we can do because the `&'a mut self`
162 // guarantees that the data is valid to mutate for at least that long.
163 unsafe {
164 let children_ptr = &raw mut (*ptr).children;
165 &mut *children_ptr
166 }
167 }
168
169 /// Gets a mutable reference to the attachments.
170 ///
171 /// # Safety
172 ///
173 /// The caller must ensure that this is the only existing reference pointing to
174 /// the inner [`ReportData`].
175 pub fn attachments_mut(self) -> &'a mut Vec<RawAttachment> {
176 let ptr = self.into_mut_ptr();
177
178 // SAFETY: We don't know the actual inner context type, but we do know
179 // that it points to an instance of `ReportData<C>` for some specific `C`.
180 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
181 // the fields before the actual context.
182 //
183 // We need to take care to avoid creating an actual reference to
184 // the `ReportData` itself though, as that would still be undefined behavior
185 // since we don't have the right type.
186 //
187 // We return a mutable pointer with lifetime `'a`, which we can do because the `&'a mut self`
188 // guarantees that the data is valid to mutate for at least that long.
189 unsafe {
190 let attachments_ptr = &raw mut (*ptr).attachments;
191 &mut *attachments_ptr
192 }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_report_data_field_offsets() {
202 // Test that fields are accessible in the expected order for type-erased access
203 use core::mem::{offset_of, size_of};
204
205 fn check<T>() {
206 // Verify field order: vtable, children, attachments, context
207 assert_eq!(offset_of!(ReportData<T>, vtable), 0);
208 assert_eq!(
209 offset_of!(ReportData<T>, children),
210 size_of::<&'static ReportVtable>()
211 );
212 assert_eq!(
213 offset_of!(ReportData<T>, attachments),
214 size_of::<&'static ReportVtable>() + size_of::<Vec<RawAttachment>>()
215 );
216 assert!(
217 offset_of!(ReportData<T>, context)
218 >= size_of::<&'static ReportVtable>()
219 + size_of::<Vec<RawAttachment>>()
220 + size_of::<Vec<RawReport>>()
221 );
222 }
223
224 #[repr(align(32))]
225 struct LargeAlignment {
226 _value: u8,
227 }
228
229 check::<u8>();
230 check::<i32>();
231 check::<[u64; 4]>();
232 check::<i32>();
233 check::<LargeAlignment>();
234 }
235}