Skip to main content

rootcause_internals/report/
vtable.rs

1//! Vtable for type-erased report operations.
2//!
3//! This module contains the [`ReportVtable`] which enables calling handler
4//! methods on reports when their concrete context type `C` and handler type `H`
5//! have been erased. The vtable stores function pointers that dispatch to the
6//! correct typed implementations.
7//!
8//! This module encapsulates the fields of [`ReportVtable`] so they cannot be
9//! accessed directly. This visibility restriction guarantees the safety
10//! invariant: **the vtable's type parameters must match the actual report
11//! context type and handler stored in the `ReportData`**.
12//!
13//! # Safety Invariant
14//!
15//! This invariant is maintained because vtables are created as `&'static`
16//! references via [`ReportVtable::new`], which pairs the function pointers
17//! with specific types `C` and `H` at compile time.
18
19use core::{
20    any::{self, TypeId},
21    ptr::NonNull,
22};
23
24use crate::{
25    handlers::{ContextFormattingStyle, ContextHandler, FormattingFunction},
26    report::{
27        data::ReportData,
28        raw::{RawReport, RawReportRef},
29    },
30    util::Erased,
31};
32
33/// Vtable for type-erased report operations.
34///
35/// Contains function pointers for performing operations on reports without
36/// knowing their concrete type at compile time.
37///
38/// # Safety
39///
40/// The following safety invariants are guaranteed to be upheld as long as this
41/// struct exists:
42///
43/// * The fields `drop`, `clone_arc`, `strong_count`, `source`, `display`,
44///   `debug`, and `preferred_context_formatting_style` all point to the
45///   functions defined below
46/// * The concrete pointers are all instantiated with the same context type `C`
47///   and handler type `H` that were used to create this `ReportVtable`.
48pub(crate) struct ReportVtable {
49    /// Gets the [`TypeId`] of the context type that was used to create this
50    /// [`ReportVtable`].
51    type_id: fn() -> TypeId,
52    /// Gets the [`any::type_name`] of the context type that was used to
53    /// create this [`ReportVtable`].
54    type_name: fn() -> &'static str,
55    /// Gets the [`TypeId`] of the handler that was used to create this
56    /// [`ReportVtable`].
57    handler_type_id: fn() -> TypeId,
58    /// Method to drop the [`triomphe::Arc<ReportData<C>>`] instance pointed to
59    /// by this pointer.
60    drop: unsafe fn(NonNull<ReportData<Erased>>),
61    /// Clones the `triomphe::Arc<ReportData<C>>` pointed to by this pointer.
62    clone_arc: unsafe fn(NonNull<ReportData<Erased>>) -> RawReport,
63    /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
64    /// by this pointer.
65    strong_count: unsafe fn(RawReportRef<'_>) -> usize,
66    /// Returns a reference to the source of the error using the `source` method
67    /// on the handler.
68    source: unsafe fn(RawReportRef<'_>) -> Option<&(dyn core::error::Error + 'static)>,
69    /// Formats the report using the `display` method on the handler.
70    display: unsafe fn(RawReportRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
71    /// Formats the report using the `debug` method on the handler.
72    debug: unsafe fn(RawReportRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
73    /// Get the formatting style preferred by the context when formatted as part
74    /// of a report.
75    preferred_context_formatting_style:
76        unsafe fn(RawReportRef<'_>, FormattingFunction) -> ContextFormattingStyle,
77}
78
79impl ReportVtable {
80    /// Creates a new [`ReportVtable`] for the context type `C` and the handler
81    /// type `H`.
82    pub(super) const fn new<C: 'static, H: ContextHandler<C>>() -> &'static Self {
83        const {
84            &Self {
85                type_id: TypeId::of::<C>,
86                type_name: any::type_name::<C>,
87                handler_type_id: TypeId::of::<H>,
88                drop: drop::<C>,
89                clone_arc: clone_arc::<C>,
90                strong_count: strong_count::<C>,
91                source: source::<C, H>,
92                display: display::<C, H>,
93                debug: debug::<C, H>,
94                preferred_context_formatting_style: preferred_context_formatting_style::<C, H>,
95            }
96        }
97    }
98
99    /// Gets the [`TypeId`] of the context type that was used to create this
100    /// [`ReportVtable`].
101    #[inline]
102    pub(super) fn type_id(&self) -> TypeId {
103        (self.type_id)()
104    }
105
106    /// Gets the [`any::type_name`] of the context type that was used to create
107    /// this [`ReportVtable`].
108    #[inline]
109    pub(super) fn type_name(&self) -> &'static str {
110        (self.type_name)()
111    }
112
113    /// Gets the [`TypeId`] of the handler that was used to create this
114    /// [`ReportVtable`].
115    #[inline]
116    pub(super) fn handler_type_id(&self) -> TypeId {
117        (self.handler_type_id)()
118    }
119
120    /// Drops the `triomphe::Arc<ReportData<C>>` instance pointed to by this
121    /// pointer.
122    ///
123    /// # Safety
124    ///
125    /// The caller must ensure:
126    ///
127    /// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into
128    ///    a pointer via [`triomphe::Arc::into_raw`]
129    /// 2. This [`ReportVtable`] must be a vtable for the context type stored in
130    ///    the [`ReportData`].
131    /// 3. This method drops the [`triomphe::Arc<ReportData<C>>`], so the caller
132    ///    must ensure that the pointer has not previously been dropped, that it
133    ///    is able to transfer ownership of the pointer, and that it will not
134    ///    use the pointer after calling this method.
135    #[inline]
136    pub(super) unsafe fn drop(&self, ptr: NonNull<ReportData<Erased>>) {
137        // SAFETY: We know that `self.drop` points to the function `drop::<C>` below.
138        // That function's safety requirements are upheld:
139        // 1. Guaranteed by the caller
140        // 2. Guaranteed by the caller
141        // 3. Guaranteed by the caller
142        unsafe {
143            // @add-unsafe-context: drop
144            // @add-unsafe-context: RawReportRef
145            // @add-unsafe-context: ReportData
146            (self.drop)(ptr);
147        }
148    }
149
150    /// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
151    ///
152    /// # Safety
153    ///
154    /// The caller must ensure:
155    ///
156    /// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into
157    ///    a pointer via [`triomphe::Arc::into_raw`]
158    /// 2. The pointer has full provenance over the `Arc` (i.e., it was not
159    ///    derived from a `&T` reference)
160    /// 3. This [`ReportVtable`] must be a vtable for the context type stored in
161    ///    the [`ReportData`].
162    /// 4. All other references to this report are compatible with shared
163    ///    ownership. Specifically none of them assume that the strong_count is
164    ///    `1`.
165    #[inline]
166    pub(super) unsafe fn clone_arc(&self, ptr: NonNull<ReportData<Erased>>) -> RawReport {
167        // SAFETY: We know that `self.clone_arc` points to the function `clone_arc::<C>`
168        // below. That function's safety requirements are upheld:
169        // 1. Guaranteed by the caller
170        // 2. Guaranteed by the caller
171        // 3. Guaranteed by the caller
172        // 4. Guaranteed by the caller
173        unsafe {
174            // @add-unsafe-context: clone_arc
175            // @add-unsafe-context: RawReportRef
176            // @add-unsafe-context: ReportData
177            (self.clone_arc)(ptr)
178        }
179    }
180
181    /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
182    /// by this pointer.
183    ///
184    /// # Safety
185    ///
186    /// The caller must ensure:
187    ///
188    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
189    ///    the [`RawReportRef`].
190    #[inline]
191    pub(super) unsafe fn strong_count<'a>(&self, ptr: RawReportRef<'a>) -> usize {
192        // SAFETY: We know that `self.strong_count` points to the function
193        // `strong_count::<C>` below. That function's safety requirements are
194        // upheld:
195        // 1. Guaranteed by the caller
196        unsafe {
197            // @add-unsafe-context: strong_count
198            // @add-unsafe-context: RawReportRef
199            // @add-unsafe-context: ReportData
200            (self.strong_count)(ptr)
201        }
202    }
203
204    /// Returns a reference to the source of the error using the [`H::source`]
205    /// function used when creating this [`ReportVtable`].
206    ///
207    /// # Safety
208    ///
209    /// The caller must ensure:
210    ///
211    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
212    ///    the [`RawReportRef`].
213    ///
214    /// [`H::source`]: ContextHandler::source
215    #[inline]
216    pub(super) unsafe fn source<'a>(
217        &self,
218        ptr: RawReportRef<'a>,
219    ) -> Option<&'a (dyn core::error::Error + 'static)> {
220        // SAFETY: We know that `self.source` points to the function `source::<C, H>`
221        // below. That function's safety requirements are upheld:
222        // 1. Guaranteed by the caller
223        unsafe {
224            // @add-unsafe-context: source
225            // @add-unsafe-context: RawReportRef
226            // @add-unsafe-context: ReportData
227            (self.source)(ptr)
228        }
229    }
230
231    /// Formats the report using the [`H::display`] function
232    /// used when creating this [`ReportVtable`].
233    ///
234    /// [`H::display`]: ContextHandler::display
235    ///
236    /// # Safety
237    ///
238    /// The caller must ensure:
239    ///
240    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
241    ///    the [`RawReportRef`].
242    #[inline]
243    pub(super) unsafe fn display(
244        &self,
245        ptr: RawReportRef<'_>,
246        formatter: &mut core::fmt::Formatter<'_>,
247    ) -> core::fmt::Result {
248        // SAFETY: We know that `self.display` points to the function `display::<C, H>`
249        // below. That function's safety requirements are upheld:
250        // 1. Guaranteed by the caller
251        unsafe {
252            // @add-unsafe-context: display
253            // @add-unsafe-context: RawReportRef
254            // @add-unsafe-context: ReportData
255            (self.display)(ptr, formatter)
256        }
257    }
258
259    /// Formats the given `RawReportRef` using the [`H::debug`] function
260    /// used when creating this [`ReportVtable`].
261    ///
262    /// [`H::debug`]: ContextHandler::debug
263    ///
264    /// # Safety
265    ///
266    /// The caller must ensure:
267    ///
268    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
269    ///    the [`RawReportRef`].
270    #[inline]
271    pub(super) unsafe fn debug(
272        &self,
273        ptr: RawReportRef<'_>,
274        formatter: &mut core::fmt::Formatter<'_>,
275    ) -> core::fmt::Result {
276        // SAFETY: We know that `self.debug` points to the function `debug::<C, H>`
277        // below. That function's safety requirements are upheld:
278        // 1. Guaranteed by the caller
279        unsafe {
280            // @add-unsafe-context: debug
281            // @add-unsafe-context: RawReportRef
282            // @add-unsafe-context: ReportData
283            (self.debug)(ptr, formatter)
284        }
285    }
286
287    /// Calls the [`H::preferred_formatting_style`] function to get the
288    /// formatting style preferred by the context when formatted as part of
289    /// a report.
290    ///
291    /// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
292    ///
293    /// # Safety
294    ///
295    /// The caller must ensure:
296    ///
297    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
298    ///    the [`RawReportRef`].
299    #[inline]
300    pub(super) unsafe fn preferred_context_formatting_style(
301        &self,
302        ptr: RawReportRef<'_>,
303        report_formatting_function: FormattingFunction,
304    ) -> ContextFormattingStyle {
305        // SAFETY: We know that `self.preferred_context_formatting_style` points to the
306        // function `preferred_context_formatting_style::<C, H>` below.
307        // That function's safety requirements are upheld:
308        // 1. Guaranteed by the caller
309        unsafe {
310            // @add-unsafe-context: preferred_context_formatting_style
311            (self.preferred_context_formatting_style)(ptr, report_formatting_function)
312        }
313    }
314}
315
316/// Drops the [`triomphe::Arc<ReportData<C>>`] instance pointed to by this
317/// pointer.
318///
319/// # Safety
320///
321/// The caller must ensure:
322///
323/// 1. The pointer comes from [`triomphe::Arc<ReportData<C>>`] via
324///    [`triomphe::Arc::into_raw`]
325/// 2. The context type `C` matches the actual context type stored in the
326///    [`ReportData`]
327/// 3. This method drops the [`triomphe::Arc<ReportData<C>>`], so the caller
328///    must ensure that the pointer has not previously been dropped, that it is
329///    able to transfer ownership of the pointer, and that it will not use the
330///    pointer after calling this method.
331pub(super) unsafe fn drop<C: 'static>(ptr: NonNull<ReportData<Erased>>) {
332    let ptr: NonNull<ReportData<C>> = ptr.cast();
333    let ptr = ptr.as_ptr();
334    // SAFETY:
335    // 1. The pointer has the correct type and came from `Arc::into_raw` (guaranteed
336    //    by caller)
337    // 2. After `from_raw`, the pointer is consumed and not accessed again
338    let arc = unsafe {
339        // @add-unsafe-context: ReportData
340        triomphe::Arc::from_raw(ptr)
341    };
342    core::mem::drop(arc);
343}
344
345/// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
346///
347/// # Safety
348///
349/// The caller must ensure:
350///
351/// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into a
352///    pointer via [`triomphe::Arc::into_raw`]
353/// 2. The pointer has full provenance over the `Arc` (i.e., it was not derived
354///    from a `&T` reference)
355/// 3. The context type `C` matches the actual context type stored in the
356///    [`ReportData`]
357/// 4. All other references to this report are compatible with shared ownership.
358///    Specifically none of them assume that the strong_count is `1`.
359unsafe fn clone_arc<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> RawReport {
360    let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
361
362    // SAFETY:
363    // - The pointer is valid and came from `Arc::into_raw` with the correct type
364    //   (guaranteed by the caller)
365    // - The pointer has full provenance over the `Arc` (i.e., it was not derived
366    //   from a `&T` reference) (guaranteed by the caller)
367    let arc_borrow = unsafe {
368        // @add-unsafe-context: ReportData
369        triomphe::ArcBorrow::from_ptr(ptr)
370    };
371
372    let arc = arc_borrow.clone_arc();
373    RawReport::from_arc(arc)
374}
375
376/// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to by
377/// this pointer.
378///
379/// # Safety
380///
381/// The caller must ensure:
382///
383/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
384unsafe fn strong_count<'a, C: 'static>(ptr: RawReportRef<'a>) -> usize {
385    let ptr: *const ReportData<C> = ptr.as_ptr().cast::<ReportData<C>>();
386
387    // SAFETY: The pointer is valid and came from `Arc::into_raw` with the correct
388    // type (guaranteed by the caller), which fulfills the requirements for
389    // `ArcBorrow::from_ptr`.
390    let arc_borrow = unsafe {
391        // @add-unsafe-context: ReportData
392        triomphe::ArcBorrow::from_ptr(ptr)
393    };
394
395    triomphe::ArcBorrow::strong_count(&arc_borrow)
396}
397
398/// Gets the source error from a report using its handler's source
399/// implementation.
400///
401/// # Safety
402///
403/// The caller must ensure:
404///
405/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
406unsafe fn source<'a, C: 'static, H: ContextHandler<C>>(
407    ptr: RawReportRef<'a>,
408) -> Option<&'a (dyn core::error::Error + 'static)> {
409    // SAFETY:
410    // 1. Guaranteed by the caller
411    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
412    H::source(context)
413}
414
415/// Formats a report using its handler's display implementation.
416///
417/// # Safety
418///
419/// The caller must ensure:
420///
421/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
422unsafe fn display<C: 'static, H: ContextHandler<C>>(
423    ptr: RawReportRef<'_>,
424    formatter: &mut core::fmt::Formatter<'_>,
425) -> core::fmt::Result {
426    // SAFETY:
427    // 1. Guaranteed by the caller
428    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
429    H::display(context, formatter)
430}
431
432/// Formats a report using its handler's debug implementation.
433///
434/// # Safety
435///
436/// The caller must ensure:
437///
438/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
439unsafe fn debug<C: 'static, H: ContextHandler<C>>(
440    ptr: RawReportRef<'_>,
441    formatter: &mut core::fmt::Formatter<'_>,
442) -> core::fmt::Result {
443    // SAFETY:
444    // 1. Guaranteed by the caller
445    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
446    H::debug(context, formatter)
447}
448
449/// Gets the preferred formatting style using the
450/// [`H::preferred_formatting_style`] function.
451///
452/// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
453///
454/// # Safety
455///
456/// The caller must ensure:
457///
458/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
459unsafe fn preferred_context_formatting_style<C: 'static, H: ContextHandler<C>>(
460    ptr: RawReportRef<'_>,
461    report_formatting_function: FormattingFunction,
462) -> ContextFormattingStyle {
463    // SAFETY:
464    // 1. Guaranteed by the caller
465    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
466    H::preferred_formatting_style(context, report_formatting_function)
467}
468
469#[cfg(test)]
470mod tests {
471    use alloc::vec;
472    use core::{error::Error, fmt};
473
474    use super::*;
475    use crate::{handlers::ContextHandler, report::RawReport};
476
477    struct HandlerI32;
478    impl ContextHandler<i32> for HandlerI32 {
479        fn source(_value: &i32) -> Option<&(dyn Error + 'static)> {
480            None
481        }
482
483        fn display(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
484            fmt::Display::fmt(value, formatter)
485        }
486
487        fn debug(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
488            fmt::Debug::fmt(value, formatter)
489        }
490    }
491
492    #[test]
493    fn test_report_vtable_eq() {
494        // Test that vtables have proper static lifetime and can be safely shared
495        let vtable1 = ReportVtable::new::<i32, HandlerI32>();
496        let vtable2 = ReportVtable::new::<i32, HandlerI32>();
497
498        // Both should be the exact same static instance
499        assert!(core::ptr::eq(vtable1, vtable2));
500    }
501
502    #[test]
503    fn test_report_type_id() {
504        let vtable = ReportVtable::new::<i32, HandlerI32>();
505        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
506    }
507
508    #[test]
509    fn test_report_type_name() {
510        let vtable = ReportVtable::new::<i32, HandlerI32>();
511        assert_eq!(vtable.type_name(), core::any::type_name::<i32>());
512    }
513
514    #[test]
515    fn test_report_clone_eq() {
516        let report = RawReport::new::<_, HandlerI32>(42, vec![], vec![]);
517
518        // SAFETY: There are no assumptions about single ownership
519        let cloned_report = unsafe { report.as_ref().clone_arc() };
520
521        // Both reports should point to the same underlying data
522        assert!(core::ptr::eq(
523            report.as_ref().as_ptr(),
524            cloned_report.as_ref().as_ptr()
525        ));
526    }
527}