rootcause_internals/report/
vtable.rs

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