rootcause_internals/report/
vtable.rs

1use core::{any::TypeId, ptr::NonNull};
2
3use crate::{
4    handlers::{ContextFormattingStyle, ContextHandler, FormattingFunction},
5    report::{
6        data::ReportData,
7        raw::{RawReport, RawReportRef},
8    },
9    util::Erased,
10};
11
12/// Vtable for type-erased report operations.
13///
14/// Contains function pointers for performing operations on reports without
15/// knowing their concrete type at compile time.
16pub(super) struct ReportVtable {
17    type_id: fn() -> TypeId,
18    handler_type_id: fn() -> TypeId,
19    drop: unsafe fn(NonNull<ReportData<Erased>>),
20    clone_arc: unsafe fn(NonNull<ReportData<Erased>>) -> RawReport,
21    strong_count: unsafe fn(NonNull<ReportData<Erased>>) -> usize,
22    source: unsafe fn(RawReportRef<'_>) -> Option<&(dyn core::error::Error + 'static)>,
23    display: unsafe fn(RawReportRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
24    debug: unsafe fn(RawReportRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
25    preferred_context_formatting_style:
26        unsafe fn(RawReportRef<'_>, FormattingFunction) -> ContextFormattingStyle,
27}
28
29impl ReportVtable {
30    /// Creates a new [`ReportVtable`] for the context type `C` and the handler type `H`.
31    pub(super) const fn new<C: 'static, H: ContextHandler<C>>() -> &'static Self {
32        &Self {
33            type_id: TypeId::of::<C>,
34            handler_type_id: TypeId::of::<H>,
35            drop: drop::<C>,
36            clone_arc: clone_arc::<C>,
37            strong_count: strong_count::<C>,
38            source: source::<C, H>,
39            display: display::<C, H>,
40            debug: debug::<C, H>,
41            preferred_context_formatting_style: preferred_context_formatting_style::<C, H>,
42        }
43    }
44
45    /// Gets the [`TypeId`] of the context type that was used to create this [`ReportVtable`].
46    pub(super) fn type_id(&self) -> TypeId {
47        (self.type_id)()
48    }
49
50    /// Gets the [`TypeId`] of the handler that was used to create this [`ReportVtable`].
51    pub(super) fn handler_type_id(&self) -> TypeId {
52        (self.handler_type_id)()
53    }
54
55    /// Drops the [`triomphe::Arc<ReportData<C>>`] instance pointed to by this pointer.
56    ///
57    /// # Safety
58    ///
59    /// - The caller must ensure that the pointer comes from an [`triomphe::Arc<ReportData<C>>`], which was turned
60    ///   into a pointer using [`triomphe::Arc::into_raw`].
61    /// - The context type `C` stored in the [`ReportData`] must match the `C` used when creating this [`ReportVtable`].
62    /// - After calling this method, the pointer must no longer be used.
63    pub(super) unsafe fn drop(&self, ptr: NonNull<ReportData<Erased>>) {
64        // SAFETY: We know that `self.drop` points to the function `drop::<C>` below.
65        // That function has three requirements, all of which are guaranteed by our caller:
66        // - The pointer must come from `triomphe::Arc::into_raw`
67        // - The context type `C` must match the stored type
68        // - The pointer must not be used after calling
69        unsafe {
70            (self.drop)(ptr);
71        }
72    }
73
74    /// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
75    ///
76    /// # Safety
77    ///
78    /// - The caller must ensure that the pointer comes from an [`triomphe::Arc<ReportData<C>>`], which was turned
79    ///   into a pointer using [`triomphe::Arc::into_raw`].
80    /// - The context type `C` stored in the [`ReportData`] must match the `C` used when creating this [`ReportVtable`].
81    /// - There must be no external assumptions that there is a unique ownership of the [`triomphe::Arc`].
82    pub(super) unsafe fn clone_arc(&self, ptr: NonNull<ReportData<Erased>>) -> RawReport {
83        // SAFETY: We know that `self.clone_arc` points to the function `clone_arc::<C>` below.
84        // That function has three requirements, all of which are guaranteed by our caller:
85        // - The pointer must come from `triomphe::Arc::into_raw`
86        // - The context type `C` must match the stored type
87        // - There must be no external assumptions about pointer uniqueness
88        unsafe { (self.clone_arc)(ptr) }
89    }
90
91    /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
92    ///
93    /// # Safety
94    ///
95    /// - The caller must ensure that the pointer comes from an [`triomphe::Arc<ReportData<C>>`], which was turned
96    ///   into a pointer using [`triomphe::Arc::into_raw`].
97    /// - The context type `C` stored in the [`ReportData`] must match the `C` used when creating this [`ReportVtable`].
98    pub(super) unsafe fn strong_count(&self, ptr: NonNull<ReportData<Erased>>) -> usize {
99        // SAFETY: We know that `self.clone_arc` points to the function `clone_arc::<C>` below.
100        // That function has three requirements, all of which are guaranteed by our caller:
101        // - The pointer must come from `triomphe::Arc::into_raw`
102        // - The context type `C` must match the stored type
103        unsafe { (self.strong_count)(ptr) }
104    }
105
106    /// Returns a reference to the source of the error using the [`H::source`] function
107    /// used when creating this [`ReportVtable`].
108    ///
109    /// # Safety
110    ///
111    /// The context type `C` used when creating this [`ReportVtable`] must match the type
112    /// of the `C` stored in the [`ReportData`] pointed to by the [`RawReportRef`].
113    ///
114    /// [`H::source`]: ContextHandler::source
115    pub(super) unsafe fn source<'a>(
116        &self,
117        ptr: RawReportRef<'a>,
118    ) -> Option<&'a (dyn core::error::Error + 'static)> {
119        // SAFETY: We know that the `self.source` field points to the function `source::<C>` below.
120        // The safety requirement of that function is that the `C` matches the one stored in the
121        // `ReportData` pointed to by `ptr`. This is guaranteed by the caller of this method.
122        unsafe { (self.source)(ptr) }
123    }
124
125    /// Formats the report using the [`H::display`] function
126    /// used when creating this [`ReportVtable`].
127    ///
128    /// [`H::display`]: ContextHandler::display
129    ///
130    /// # Safety
131    ///
132    /// The context type `C` used when creating this [`ReportVtable`] must match the type
133    /// stored in the [`ReportData`].
134    pub(super) unsafe fn display(
135        &self,
136        ptr: RawReportRef<'_>,
137        formatter: &mut core::fmt::Formatter<'_>,
138    ) -> core::fmt::Result {
139        // SAFETY: We know that the `self.display` field points to the function `display::<C, H>` below.
140        // That function requires that the context type `C` matches the actual context type stored in the `ReportData`,
141        // which is guaranteed by our caller.
142        unsafe { (self.display)(ptr, formatter) }
143    }
144
145    /// Formats the given `RawReportRef` using the [`H::debug`] function
146    /// used when creating this [`ReportVtable`].
147    ///
148    /// [`H::debug`]: ContextHandler::debug
149    ///
150    /// # Safety
151    ///
152    /// The context type `C` used when creating this [`ReportVtable`] must match the type
153    /// stored in the [`ReportData`].
154    pub(super) unsafe fn debug(
155        &self,
156        ptr: RawReportRef<'_>,
157        formatter: &mut core::fmt::Formatter<'_>,
158    ) -> core::fmt::Result {
159        // SAFETY: We know that the `self.debug` field points to the function `debug::<C, H>` below.
160        // That function requires that the context type `C` matches the actual context type stored in the `ReportData`,
161        // which is guaranteed by our caller.
162        unsafe { (self.debug)(ptr, formatter) }
163    }
164
165    /// Calls the [`H::preferred_formatting_style`] function to get the formatting style preferred by
166    /// the context when formatted as part of a report.
167    ///
168    /// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
169    ///
170    /// # Safety
171    ///
172    /// The context type `C` used when creating this [`ReportVtable`] must match the type
173    /// stored in the [`ReportData`].
174    pub(super) unsafe fn preferred_context_formatting_style(
175        &self,
176        ptr: RawReportRef<'_>,
177        report_formatting_function: FormattingFunction,
178    ) -> ContextFormattingStyle {
179        // SAFETY: We know that the `self.preferred_context_formatting_style` field points to the function `preferred_context_formatting_style::<C, H>` below.
180        // That function requires that the context type `C` matches the actual context type stored in the `ReportData`,
181        // which is guaranteed by our caller.
182        unsafe { (self.preferred_context_formatting_style)(ptr, report_formatting_function) }
183    }
184}
185
186/// Drops the [`triomphe::Arc<ReportData<C>>`] instance pointed to by this pointer.
187///
188/// # Safety
189///
190/// - The caller must ensure that the pointer comes from an [`triomphe::Arc<ReportData<C>>`], which was turned
191///   into a pointer using [`triomphe::Arc::into_raw`].
192/// - The context type `C` must match the actual context type stored in the [`ReportData`].
193/// - After calling this method, the pointer must no longer be used.
194pub(super) unsafe fn drop<C: 'static>(ptr: NonNull<ReportData<Erased>>) {
195    let ptr: NonNull<ReportData<C>> = ptr.cast();
196    let ptr = ptr.as_ptr();
197    // SAFETY: Triomphe has two requirements:
198    // - The given pointer must be of the correct type and have come from a call to `Arc::into_raw`.
199    // - After `from_raw`, the pointer must not be accessed.
200    //
201    // The first requirement is guaranteed by the fact that we created the pointer
202    // using `Arc::into_raw` and the caller guarantees that the type `C` matches
203    // the context type stored in the `ReportData`.
204    //
205    // The second requirement is guaranteed by the fact that there are no existing
206    // references to the same `ReportData` instance, as this method consumes the pointer.
207    let arc = unsafe { triomphe::Arc::from_raw(ptr) };
208    core::mem::drop(arc);
209}
210
211/// Clones the [`triomphe::Arc<ReportData<C>>`] instance pointed to by this pointer.
212///
213/// # Safety
214///
215/// - The caller must ensure that the pointer comes from an [`triomphe::Arc<ReportData<C>>`], which was turned
216///   into a pointer using [`triomphe::Arc::into_raw`].
217/// - The context type `C` must also match the actual context stored in the [`ReportData`].
218/// - There must be no external assumptions that there is a unique ownership of the [`triomphe::Arc`].
219unsafe fn clone_arc<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> RawReport {
220    let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
221
222    // SAFETY: Our caller guarantees that we point to a `ReportData<C>` and that this pointer came
223    // from a `triomphe::Arc::into_raw`.
224    //
225    // This fulfills the safety docs for `ArcBorrow::from_ptr`, which explicitly mentions
226    // the `as_ptr` (which is called from `into_raw`) is safe.
227    let arc_borrow = unsafe { triomphe::ArcBorrow::from_ptr(ptr) };
228
229    let arc = arc_borrow.clone_arc();
230    RawReport::from_arc(arc)
231}
232
233/// Gets the source error from a report using its handler's source implementation.
234///
235/// # Safety
236///
237/// The caller must ensure that the type `C` matches the actual context type stored in the [`ReportData`].
238unsafe fn source<'a, C: 'static, H: ContextHandler<C>>(
239    ptr: RawReportRef<'a>,
240) -> Option<&'a (dyn core::error::Error + 'static)> {
241    // SAFETY: Our caller guarantees that the type `C` matches the actual context type stored in the `ReportData`.
242    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
243    H::source(context)
244}
245
246/// Formats a report using its handler's display implementation.
247///
248/// # Safety
249///
250/// The caller must ensure that the type `C` matches the actual context type stored in the [`ReportData`].
251unsafe fn display<C: 'static, H: ContextHandler<C>>(
252    ptr: RawReportRef<'_>,
253    formatter: &mut core::fmt::Formatter<'_>,
254) -> core::fmt::Result {
255    // SAFETY: Our caller guarantees that the type `C` matches the actual context type stored in the `ReportData`.
256    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
257    H::display(context, formatter)
258}
259
260/// Formats a report using its handler's debug implementation.
261///
262/// # Safety
263///
264/// The caller must ensure that the type `C` matches the actual context type stored in the [`ReportData`].
265unsafe fn debug<C: 'static, H: ContextHandler<C>>(
266    ptr: RawReportRef<'_>,
267    formatter: &mut core::fmt::Formatter<'_>,
268) -> core::fmt::Result {
269    // SAFETY: Our caller guarantees that the type `C` matches the actual context type stored in the `ReportData`.
270    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
271    H::debug(context, formatter)
272}
273
274/// Gets the preferred formatting style using the [`H::preferred_formatting_style`] function.
275///
276/// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
277///
278/// # Safety
279///
280/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
281unsafe fn preferred_context_formatting_style<C: 'static, H: ContextHandler<C>>(
282    ptr: RawReportRef<'_>,
283    report_formatting_function: FormattingFunction,
284) -> ContextFormattingStyle {
285    // SAFETY: Our caller guarantees that the type `C` matches the actual attachment type stored in the `AttachmentData`.
286    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
287    H::preferred_formatting_style(context, report_formatting_function)
288}
289
290/// Gets the strong count of the underlying [`triomphe::Arc`]
291///
292/// # Safety
293///
294/// - The caller must ensure that the pointer comes from an [`triomphe::Arc<ReportData<C>>`], which was turned
295///   into a pointer using [`triomphe::Arc::into_raw`].
296/// - The context type `C` must also match the actual context stored in the [`ReportData`].
297unsafe fn strong_count<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> usize {
298    let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
299
300    // SAFETY: Our caller guarantees that we point to a `ReportData<C>` and that this pointer came
301    // from a `triomphe::Arc::into_raw`.
302    //
303    // This fulfills the safety docs for `ArcBorrow::from_ptr`, which explicitly mentions
304    // the `as_ptr` (which is called from `into_raw`) is safe.
305    let arc_borrow = unsafe { triomphe::ArcBorrow::from_ptr(ptr) };
306
307    triomphe::ArcBorrow::strong_count(&arc_borrow)
308}
309
310#[cfg(test)]
311mod tests {
312    use alloc::vec;
313    use core::{error::Error, fmt};
314
315    use super::*;
316    use crate::{handlers::ContextHandler, report::RawReport};
317
318    struct HandlerI32;
319    impl ContextHandler<i32> for HandlerI32 {
320        fn source(_value: &i32) -> Option<&(dyn Error + 'static)> {
321            None
322        }
323
324        fn display(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
325            fmt::Display::fmt(value, formatter)
326        }
327
328        fn debug(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
329            fmt::Debug::fmt(value, formatter)
330        }
331    }
332
333    #[test]
334    fn test_report_vtable_eq() {
335        // Test that vtables have proper static lifetime and can be safely shared
336        let vtable1 = ReportVtable::new::<i32, HandlerI32>();
337        let vtable2 = ReportVtable::new::<i32, HandlerI32>();
338
339        // Both should be the exact same static instance
340        assert!(core::ptr::eq(vtable1, vtable2));
341    }
342
343    #[test]
344    fn test_report_type_id() {
345        let vtable = ReportVtable::new::<i32, HandlerI32>();
346        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
347    }
348
349    #[test]
350    fn test_report_clone_eq() {
351        let report = RawReport::new::<_, HandlerI32>(42, vec![], vec![]);
352
353        // SAFETY: There are no assumptions about single ownership
354        let cloned_report = unsafe { report.as_ref().clone_arc() };
355
356        // Both reports should be the same after
357        assert!(core::ptr::eq(
358            report.as_ref().as_ptr(),
359            cloned_report.as_ref().as_ptr()
360        ));
361    }
362}