rootcause_internals/report/data.rs
1//! `ReportData<C>` wrapper and field access.
2//!
3//! This module encapsulates the fields of [`ReportData`], ensuring they are
4//! only visible within this module. This visibility restriction guarantees the
5//! safety invariant: **the vtable type must always match the actual context
6//! type**.
7//!
8//! # Safety Invariant
9//!
10//! Since `ReportData` can only be constructed via [`ReportData::new`] (which
11//! creates matching vtable and context), and fields cannot be modified after
12//! construction (no `pub` or `pub(crate)` fields), the types remain in sync
13//! throughout the value's lifetime.
14//!
15//! # `#[repr(C)]` Layout
16//!
17//! The `#[repr(C)]` attribute enables safe field projection even when the type
18//! parameter `C` is erased. This allows accessing the vtable, children, and
19//! attachments fields from a pointer to `ReportData<Erased>` without constructing
20//! an invalid reference to the full struct.
21
22use alloc::vec::Vec;
23use core::ptr::NonNull;
24
25use crate::{
26 attachment::RawAttachment,
27 handlers::ContextHandler,
28 report::{
29 raw::{RawReport, RawReportMut, RawReportRef},
30 vtable::ReportVtable,
31 },
32 util::Erased,
33};
34
35/// Type-erased report data structure with vtable-based dispatch.
36///
37/// This struct uses `#[repr(C)]` to enable safe field access in type-erased
38/// contexts, allowing access to the vtable and other fields even when the
39/// concrete context type `C` is unknown.
40#[repr(C)]
41pub(crate) struct ReportData<C: 'static> {
42 /// Reference to the vtable of this report
43 vtable: &'static ReportVtable,
44 /// The children of this report
45 children: Vec<RawReport>,
46 /// The attachments of this report
47 attachments: Vec<RawAttachment>,
48 /// The context data of this report
49 context: C,
50}
51
52impl<C: 'static> ReportData<C> {
53 /// Creates a new [`ReportData`] with the specified handler, context,
54 /// children and attachments.
55 ///
56 /// This method creates the vtable for type-erased dispatch and pairs it
57 /// with the report data.
58 #[inline]
59 pub(super) fn new<H: ContextHandler<C>>(
60 context: C,
61 children: Vec<RawReport>,
62 attachments: Vec<RawAttachment>,
63 ) -> Self {
64 Self {
65 vtable: ReportVtable::new::<C, H>(),
66 children,
67 attachments,
68 context,
69 }
70 }
71}
72
73impl RawReport {
74 /// # Safety
75 ///
76 /// - The caller must ensure that the type `C` matches the actual context
77 /// type stored in the [`ReportData`].
78 /// - The caller must ensure that this is the only existing reference
79 /// pointing to the inner [`ReportData`].
80 pub unsafe fn into_parts<C: 'static>(self) -> (C, Vec<RawReport>, Vec<RawAttachment>) {
81 let ptr: NonNull<ReportData<Erased>> = self.into_non_null();
82 let ptr: NonNull<ReportData<C>> = ptr.cast::<ReportData<C>>();
83 let ptr: *const ReportData<C> = ptr.as_ptr();
84
85 // SAFETY: The requirements to this
86 // - The given pointer must be a valid pointer to `T` that came from
87 // [`Arc::into_raw`].
88 // - After `from_raw`, the pointer must not be accessed.
89 //
90 // Both of these are guaranteed by our caller
91 let arc: triomphe::Arc<ReportData<C>> = unsafe { triomphe::Arc::from_raw(ptr) };
92
93 match triomphe::Arc::try_unique(arc) {
94 Ok(unique) => {
95 let data = triomphe::UniqueArc::into_inner(unique);
96 (data.context, data.children, data.attachments)
97 }
98 Err(_) => {
99 // We could definitely get away with using unreachable_unchecked here in release
100 // builds, but since we don't expect anybody to use into_parts in performance-critical
101 // paths, it's probably better to just have a normal panic even in release builds.
102 unreachable!("Caller did not fulfill the guarantee that pointer is unique")
103 }
104 }
105 }
106}
107
108impl<'a> RawReportRef<'a> {
109 /// Returns a reference to the [`ReportVtable`] of the [`ReportData`]
110 /// instance.
111 #[inline]
112 pub(super) fn vtable(self) -> &'static ReportVtable {
113 let ptr = self.as_ptr();
114 // SAFETY: We don't know the actual inner context type, but we do know
115 // that it points to an instance of `ReportData<C>` for some specific `C`.
116 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
117 // the fields before the actual context.
118 //
119 // We need to take care to avoid creating an actual reference to
120 // the `ReportData` itself though, as that would still be undefined behavior
121 // since we don't have the right type.
122 let vtable_ptr: *const &'static ReportVtable = unsafe { &raw const (*ptr).vtable };
123
124 // SAFETY: The vtable_ptr is derived from a valid Arc pointer and points
125 // to an initialized `&'static ReportVtable` field. Dereferencing is safe
126 // because:
127 // - The pointer is valid and properly aligned
128 // - The vtable field is initialized in ReportData::new and never modified
129 unsafe { *vtable_ptr }
130 }
131
132 /// Returns the child reports of this report.
133 #[inline]
134 pub fn children(self) -> &'a Vec<RawReport> {
135 let ptr = self.as_ptr();
136 // SAFETY: We don't know the actual inner context type, but we do know
137 // that it points to an instance of `ReportData<C>` for some specific `C`.
138 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
139 // the fields before the actual context.
140 //
141 // We need to take care to avoid creating an actual reference to
142 // the `ReportData` itself though, as that would still be undefined behavior
143 // since we don't have the right type.
144 let children_ptr: *const Vec<RawReport> = unsafe { &raw const (*ptr).children };
145
146 // SAFETY: We turn the `*const` pointer into a `&'a` reference. This is valid
147 // because the existence of the `RawReportRef<'a>` already implies that
148 // we have readable access to the report for the 'a lifetime.
149 unsafe { &*children_ptr }
150 }
151
152 /// Returns the attachments of this report.
153 #[inline]
154 pub fn attachments(self) -> &'a Vec<RawAttachment> {
155 let ptr = self.as_ptr();
156 // SAFETY: We don't know the actual inner context type, but we do know
157 // that it points to an instance of `ReportData<C>` for some specific `C`.
158 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
159 // the fields before the actual context.
160 //
161 // We need to take care to avoid creating an actual reference to
162 // the `ReportData` itself though, as that would still be undefined behavior
163 // since we don't have the right type.
164 let attachments_ptr: *const Vec<RawAttachment> = unsafe { &raw const (*ptr).attachments };
165
166 // SAFETY: We turn the `*const` pointer into a `&'a` reference. This is valid
167 // because the existence of the `RawReportRef<'a>` already implies that
168 // we have readable access to the report for the 'a lifetime.
169 unsafe { &*attachments_ptr }
170 }
171
172 /// Accesses the inner context of the [`ReportData`] instance as a reference
173 /// to the specified type.
174 ///
175 /// # Safety
176 ///
177 /// The caller must ensure that the type `C` matches the actual context type
178 /// stored in the [`ReportData`].
179 #[inline]
180 pub unsafe fn context_downcast_unchecked<C: 'static>(self) -> &'a C {
181 // SAFETY: The inner function requires that `C` matches the type stored, but
182 // that is guaranteed by our caller.
183 let this = unsafe { self.cast_inner::<C>() };
184 &this.context
185 }
186}
187
188impl<'a> RawReportMut<'a> {
189 /// Gets a mutable reference to the child reports.
190 #[inline]
191 pub fn into_children_mut(self) -> &'a mut Vec<RawReport> {
192 let ptr = self.into_mut_ptr();
193
194 // SAFETY: We don't know the actual inner context type, but we do know
195 // that it points to an instance of `ReportData<C>` for some specific `C`.
196 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
197 // the fields before the actual context.
198 //
199 // We need to take care to avoid creating an actual reference to
200 // the `ReportData` itself though, as that would still be undefined behavior
201 // since we don't have the right type.
202 let children_ptr: *mut Vec<RawReport> = unsafe { &raw mut (*ptr).children };
203
204 // SAFETY: We turn the `*mut` pointer into a `&'a mut` reference. This is valid
205 // because the existence of the `RawReportMut<'a>` already implied that
206 // nobody else has mutable access to the report for the 'a lifetime.
207 unsafe { &mut *children_ptr }
208 }
209
210 /// Gets a mutable reference to the attachments.
211 #[inline]
212 pub fn into_attachments_mut(self) -> &'a mut Vec<RawAttachment> {
213 let ptr = self.into_mut_ptr();
214
215 // SAFETY: We don't know the actual inner context type, but we do know
216 // that it points to an instance of `ReportData<C>` for some specific `C`.
217 // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
218 // the fields before the actual context.
219 //
220 // We need to take care to avoid creating an actual reference to
221 // the `ReportData` itself though, as that would still be undefined behavior
222 // since we don't have the right type.
223 let attachments_ptr: *mut Vec<RawAttachment> = unsafe { &raw mut (*ptr).attachments };
224
225 // SAFETY: We turn the `*mut` pointer into a `&'a mut` reference. This is valid
226 // because the existence of the `RawReportMut<'a>` already implied that
227 // nobody else has mutable access to the report for the 'a lifetime.
228 unsafe { &mut *attachments_ptr }
229 }
230
231 /// Accesses the inner context of the [`ReportData`] instance as a mutable
232 /// reference to the specified type.
233 ///
234 /// # Safety
235 ///
236 /// The caller must ensure that the type `C` matches the actual context type
237 /// stored in the [`ReportData`].
238 #[inline]
239 pub unsafe fn into_context_downcast_unchecked<C: 'static>(self) -> &'a mut C {
240 // SAFETY: The inner function requires that `C` matches the type stored, but
241 // that is guaranteed by our caller.
242 let this = unsafe { self.cast_inner::<C>() };
243 &mut this.context
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_report_data_field_offsets() {
253 // Test that fields are accessible in the expected order for type-erased access
254 use core::mem::{offset_of, size_of};
255
256 fn check<T>() {
257 // Verify field order: vtable, children, attachments, context
258 assert_eq!(offset_of!(ReportData<T>, vtable), 0);
259 assert_eq!(
260 offset_of!(ReportData<T>, children),
261 size_of::<&'static ReportVtable>()
262 );
263 assert_eq!(
264 offset_of!(ReportData<T>, attachments),
265 size_of::<&'static ReportVtable>() + size_of::<Vec<RawAttachment>>()
266 );
267 assert!(
268 offset_of!(ReportData<T>, context)
269 >= size_of::<&'static ReportVtable>()
270 + size_of::<Vec<RawAttachment>>()
271 + size_of::<Vec<RawReport>>()
272 );
273 }
274
275 #[repr(align(32))]
276 struct LargeAlignment {
277 _value: u8,
278 }
279
280 check::<u8>();
281 check::<i32>();
282 check::<[u64; 4]>();
283 check::<i32>();
284 check::<LargeAlignment>();
285 }
286}