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}