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
20//! constructing 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 ///
44 /// # Safety
45 ///
46 /// The following safety invariants are guaranteed to be upheld as long as
47 /// this struct exists:
48 ///
49 /// 1. The vtable must always point to a `ReportVtable` created for the
50 /// actual context type `C` stored below. This is true even when accessed
51 /// via type-erased pointers.
52 vtable: &'static ReportVtable,
53 /// The children of this report
54 children: Vec<RawReport>,
55 /// The attachments of this report
56 attachments: Vec<RawAttachment>,
57 /// The context data of this report
58 context: C,
59}
60
61impl<C: 'static> ReportData<C> {
62 /// Creates a new [`ReportData`] with the specified handler, context,
63 /// children and attachments.
64 ///
65 /// This method creates the vtable for type-erased dispatch and pairs it
66 /// with the report data.
67 #[inline]
68 pub(super) fn new<H: ContextHandler<C>>(
69 context: C,
70 children: Vec<RawReport>,
71 attachments: Vec<RawAttachment>,
72 ) -> Self {
73 Self {
74 vtable: ReportVtable::new::<C, H>(),
75 children,
76 attachments,
77 context,
78 }
79 }
80}
81
82impl RawReport {
83 /// Deconstructs this report into its context, children, and attachments.
84 ///
85 /// # Safety
86 ///
87 /// The caller must ensure:
88 ///
89 /// 1. The type `C` matches the actual context type stored in the
90 /// [`ReportData`]
91 /// 2. This is the only existing reference pointing to the inner
92 /// [`ReportData`]
93 pub unsafe fn into_parts<C: 'static>(self) -> (C, Vec<RawReport>, Vec<RawAttachment>) {
94 let ptr: NonNull<ReportData<Erased>> = self.into_non_null();
95 let ptr: NonNull<ReportData<C>> = ptr.cast::<ReportData<C>>();
96 let ptr: *const ReportData<C> = ptr.as_ptr();
97
98 // SAFETY:
99 // 1. The pointer is valid and came from `Arc::into_raw` (guaranteed by
100 // RawReport construction)
101 // 2. After `from_raw` the `ptr` is not accessed.
102 let arc = unsafe { triomphe::Arc::<ReportData<C>>::from_raw(ptr) };
103
104 match triomphe::Arc::try_unique(arc) {
105 Ok(unique) => {
106 let data = triomphe::UniqueArc::into_inner(unique);
107 (data.context, data.children, data.attachments)
108 }
109 Err(_) => {
110 // Note: We could use `unreachable_unchecked` here in release builds for
111 // performance, but `into_parts` is not expected to be used in
112 // performance-critical paths, so a normal panic is preferable for
113 // better debugging.
114 unreachable!("Caller did not fulfill the guarantee that pointer is unique")
115 }
116 }
117 }
118}
119
120impl<'a> RawReportRef<'a> {
121 /// Returns a reference to the [`ReportVtable`] of this report.
122 ///
123 /// The returned vtable is guaranteed to match the context type stored in
124 /// the [`ReportData`].
125 #[inline]
126 pub(super) fn vtable(self) -> &'static ReportVtable {
127 let ptr = self.as_ptr();
128 // SAFETY: The safety requirements for `&raw const (*ptr).vtable` are upheld:
129 // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
130 // guaranteed by `RawReportRef`'s invariants
131 // 2. `ReportData<C>` is `#[repr(C)]`, so the `vtable` field is at a consistent
132 // offset regardless of the type parameter `C`
133 // 3. We avoid creating a reference to the full `ReportData` struct, which would
134 // be UB since we don't know the correct type parameter
135 let vtable_ptr: *const &'static ReportVtable = unsafe {
136 // @add-unsafe-context: ReportData
137 // @add-unsafe-context: crate::util::Erased
138 &raw const (*ptr).vtable
139 };
140
141 // SAFETY: The safety requirements for dereferencing `vtable_ptr` are upheld:
142 // 1. The pointer is valid and properly aligned because it points to the first
143 // field of a valid `ReportData<C>` instance
144 // 2. The `vtable` field is initialized in `ReportData::new` and never modified,
145 // so it contains a valid `&'static ReportVtable` value
146 unsafe { *vtable_ptr }
147 }
148
149 /// Returns the child reports of this report.
150 #[inline]
151 pub fn children(self) -> &'a Vec<RawReport> {
152 let ptr: *const ReportData<Erased> = self.as_ptr();
153
154 // SAFETY: The safety requirements for `&raw const (*ptr).children` are upheld:
155 // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
156 // guaranteed by `RawReportRef`'s invariants
157 // 2. `ReportData<C>` is `#[repr(C)]`, so the `children` field is at a
158 // consistent offset regardless of the type parameter `C`
159 // 3. We avoid creating a reference to the full `ReportData` struct, which would
160 // be UB since we don't know the correct type parameter
161 let children_ptr: *const Vec<RawReport> = unsafe {
162 // @add-unsafe-context: ReportData
163 // @add-unsafe-context: crate::util::Erased
164 &raw const (*ptr).children
165 };
166
167 // SAFETY: We turn the `*const` pointer into a `&'a` reference. This is valid
168 // because the existence of the `RawReportRef<'a>` already implies that
169 // we have readable access to the report for the 'a lifetime.
170 unsafe { &*children_ptr }
171 }
172
173 /// Returns the attachments of this report.
174 #[inline]
175 pub fn attachments(self) -> &'a Vec<RawAttachment> {
176 let ptr = self.as_ptr();
177
178 // SAFETY: The safety requirements for `&raw const (*ptr).attachments` are
179 // upheld:
180 // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
181 // guaranteed by `RawReportRef`'s invariants
182 // 2. `ReportData<C>` is `#[repr(C)]`, so the `attachments` field is at a
183 // consistent offset regardless of the type parameter `C`
184 // 3. We avoid creating a reference to the full `ReportData` struct, which would
185 // be UB since we don't know the correct type parameter
186 let attachments_ptr: *const Vec<RawAttachment> = unsafe {
187 // @add-unsafe-context: ReportData
188 // @add-unsafe-context: crate::util::Erased
189 &raw const (*ptr).attachments
190 };
191
192 // SAFETY: We turn the `*const` pointer into a `&'a` reference. This is valid
193 // because the existence of the `RawReportRef<'a>` already implies that
194 // we have readable access to the report for the 'a lifetime.
195 unsafe { &*attachments_ptr }
196 }
197
198 /// Downcasts the context to the specified type and returns a reference.
199 ///
200 /// # Safety
201 ///
202 /// The caller must ensure:
203 ///
204 /// 1. The type `C` matches the actual context type stored in the
205 /// [`ReportData`]
206 #[inline]
207 pub unsafe fn context_downcast_unchecked<C: 'static>(self) -> &'a C {
208 // SAFETY:
209 // 1. Guaranteed by the caller
210 let this = unsafe { self.cast_inner::<C>() };
211 &this.context
212 }
213}
214
215impl<'a> RawReportMut<'a> {
216 /// Gets a mutable reference to the child reports.
217 ///
218 /// # Safety
219 ///
220 /// The caller must ensure:
221 ///
222 /// 1. In case there are other references to the same report and they make
223 /// assumptions about the report children being `Send+Sync`, then those
224 /// assumptions must be upheld when modifying the children.
225 #[inline]
226 pub unsafe fn into_children_mut(self) -> &'a mut Vec<RawReport> {
227 let ptr = self.into_mut_ptr();
228
229 // SAFETY: The safety requirements for `&raw mut (*ptr).children` are upheld:
230 // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
231 // guaranteed by `RawReportMut`'s invariants
232 // 2. `ReportData<C>` is `#[repr(C)]`, so the `children` field is at a
233 // consistent offset regardless of the type parameter `C`
234 // 3. We avoid creating a reference to the full `ReportData` struct, which would
235 // be UB since we don't know the correct type parameter
236 let children_ptr: *mut Vec<RawReport> = unsafe {
237 // @add-unsafe-context: ReportData
238 // @add-unsafe-context: crate::util::Erased
239 &raw mut (*ptr).children
240 };
241
242 // SAFETY: We turn the `*mut` pointer into a `&'a mut` reference. This is valid
243 // because the existence of the `RawReportMut<'a>` already implied that
244 // nobody else has mutable access to the report for the 'a lifetime.
245 unsafe { &mut *children_ptr }
246 }
247
248 /// Deconstructs the `RawReportMut` and returns a mutable reference to the
249 /// attachments vector.
250 ///
251 /// # Safety
252 ///
253 /// The caller must ensure:
254 ///
255 /// 1. In case there are other references to the same report and they make
256 /// assumptions about the report attachments being `Send+Sync`, then
257 /// those assumptions must be upheld when modifying the attachments.
258 #[inline]
259 pub unsafe fn into_attachments_mut(self) -> &'a mut Vec<RawAttachment> {
260 let ptr = self.into_mut_ptr();
261
262 // SAFETY: The safety requirements for `&raw mut (*ptr).attachments` are upheld:
263 // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
264 // guaranteed by `RawReportMut`'s invariants
265 // 2. `ReportData<C>` is `#[repr(C)]`, so the `attachments` field is at a
266 // consistent offset regardless of the type parameter `C`
267 // 3. We avoid creating a reference to the full `ReportData` struct, which would
268 // be UB since we don't know the correct type parameter
269 let attachments_ptr: *mut Vec<RawAttachment> = unsafe {
270 // @add-unsafe-context: ReportData
271 // @add-unsafe-context: crate::util::Erased
272 &raw mut (*ptr).attachments
273 };
274
275 // SAFETY: We turn the `*mut` pointer into a `&'a mut` reference. This is valid
276 // because the existence of the `RawReportMut<'a>` already implied that
277 // nobody else has mutable access to the report for the 'a lifetime.
278 unsafe { &mut *attachments_ptr }
279 }
280
281 /// Downcasts the context to the specified type and returns a mutable
282 /// reference.
283 ///
284 /// # Safety
285 ///
286 /// The caller must ensure:
287 ///
288 /// 1. The type `C` matches the actual context type stored in the
289 /// [`ReportData`]
290 #[inline]
291 pub unsafe fn into_context_downcast_unchecked<C: 'static>(self) -> &'a mut C {
292 // SAFETY:
293 // 1. Guaranteed by the caller
294 let this = unsafe { self.cast_inner::<C>() };
295 &mut this.context
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn test_report_data_field_offsets() {
305 // Test that fields are accessible in the expected order for type-erased access
306 use core::mem::{offset_of, size_of};
307
308 fn check<T>() {
309 // Verify field order: vtable, children, attachments, context
310 assert_eq!(offset_of!(ReportData<T>, vtable), 0);
311 assert_eq!(
312 offset_of!(ReportData<T>, children),
313 size_of::<&'static ReportVtable>()
314 );
315 assert_eq!(
316 offset_of!(ReportData<T>, attachments),
317 size_of::<&'static ReportVtable>() + size_of::<Vec<RawAttachment>>()
318 );
319 assert!(
320 offset_of!(ReportData<T>, context)
321 >= size_of::<&'static ReportVtable>()
322 + size_of::<Vec<RawAttachment>>()
323 + size_of::<Vec<RawReport>>()
324 );
325 }
326
327 #[repr(align(32))]
328 struct LargeAlignment {
329 _value: u8,
330 }
331
332 check::<u8>();
333 check::<i32>();
334 check::<[u64; 4]>();
335 check::<i32>();
336 check::<LargeAlignment>();
337 }
338}