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, Any, TypeId},
21    ptr::NonNull,
22};
23
24use crate::{
25    handlers::{ContextFormattingStyle, ContextHandler, FormattingFunction},
26    report::{
27        data::ReportData,
28        raw::{RawReport, RawReportMut, 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    /// Returns a `&dyn Any` view of the context.
78    context_as_any: unsafe fn(RawReportRef<'_>) -> &(dyn Any + 'static),
79    /// Returns a `&mut dyn Any` view of the context.
80    context_as_any_mut: unsafe fn(RawReportMut<'_>) -> &mut (dyn Any + 'static),
81}
82
83impl ReportVtable {
84    /// Creates a new [`ReportVtable`] for the context type `C` and the handler
85    /// type `H`.
86    pub(super) const fn new<C: 'static, H: ContextHandler<C>>() -> &'static Self {
87        const {
88            &Self {
89                type_id: TypeId::of::<C>,
90                type_name: any::type_name::<C>,
91                handler_type_id: TypeId::of::<H>,
92                drop: drop::<C>,
93                clone_arc: clone_arc::<C>,
94                strong_count: strong_count::<C>,
95                source: source::<C, H>,
96                display: display::<C, H>,
97                debug: debug::<C, H>,
98                preferred_context_formatting_style: preferred_context_formatting_style::<C, H>,
99                context_as_any: context_as_any::<C>,
100                context_as_any_mut: context_as_any_mut::<C>,
101            }
102        }
103    }
104
105    /// Gets the [`TypeId`] of the context type that was used to create this
106    /// [`ReportVtable`].
107    #[inline]
108    pub(super) fn type_id(&self) -> TypeId {
109        (self.type_id)()
110    }
111
112    /// Gets the [`any::type_name`] of the context type that was used to create
113    /// this [`ReportVtable`].
114    #[inline]
115    pub(super) fn type_name(&self) -> &'static str {
116        (self.type_name)()
117    }
118
119    /// Gets the [`TypeId`] of the handler that was used to create this
120    /// [`ReportVtable`].
121    #[inline]
122    pub(super) fn handler_type_id(&self) -> TypeId {
123        (self.handler_type_id)()
124    }
125
126    /// Drops the `triomphe::Arc<ReportData<C>>` instance pointed to by this
127    /// pointer.
128    ///
129    /// # Safety
130    ///
131    /// The caller must ensure:
132    ///
133    /// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into
134    ///    a pointer via [`triomphe::Arc::into_raw`]
135    /// 2. This [`ReportVtable`] must be a vtable for the context type stored in
136    ///    the [`ReportData`].
137    /// 3. This method drops the [`triomphe::Arc<ReportData<C>>`], so the caller
138    ///    must ensure that the pointer has not previously been dropped, that it
139    ///    is able to transfer ownership of the pointer, and that it will not
140    ///    use the pointer after calling this method.
141    #[inline]
142    pub(super) unsafe fn drop(&self, ptr: NonNull<ReportData<Erased>>) {
143        // SAFETY: We know that `self.drop` points to the function `drop::<C>` below.
144        // That function's safety requirements are upheld:
145        // 1. Guaranteed by the caller
146        // 2. Guaranteed by the caller
147        // 3. Guaranteed by the caller
148        unsafe {
149            // @add-unsafe-context: drop
150            // @add-unsafe-context: RawReportRef
151            // @add-unsafe-context: ReportData
152            (self.drop)(ptr);
153        }
154    }
155
156    /// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
157    ///
158    /// # Safety
159    ///
160    /// The caller must ensure:
161    ///
162    /// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into
163    ///    a pointer via [`triomphe::Arc::into_raw`]
164    /// 2. The pointer has full provenance over the `Arc` (i.e., it was not
165    ///    derived from a `&T` reference)
166    /// 3. This [`ReportVtable`] must be a vtable for the context type stored in
167    ///    the [`ReportData`].
168    /// 4. All other references to this report are compatible with shared
169    ///    ownership. Specifically none of them assume that the strong_count is
170    ///    `1`.
171    #[inline]
172    pub(super) unsafe fn clone_arc(&self, ptr: NonNull<ReportData<Erased>>) -> RawReport {
173        // SAFETY: We know that `self.clone_arc` points to the function `clone_arc::<C>`
174        // below. That function's safety requirements are upheld:
175        // 1. Guaranteed by the caller
176        // 2. Guaranteed by the caller
177        // 3. Guaranteed by the caller
178        // 4. Guaranteed by the caller
179        unsafe {
180            // @add-unsafe-context: clone_arc
181            // @add-unsafe-context: RawReportRef
182            // @add-unsafe-context: ReportData
183            (self.clone_arc)(ptr)
184        }
185    }
186
187    /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
188    /// by this pointer.
189    ///
190    /// # Safety
191    ///
192    /// The caller must ensure:
193    ///
194    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
195    ///    the [`RawReportRef`].
196    #[inline]
197    pub(super) unsafe fn strong_count<'a>(&self, ptr: RawReportRef<'a>) -> usize {
198        // SAFETY: We know that `self.strong_count` points to the function
199        // `strong_count::<C>` below. That function's safety requirements are
200        // upheld:
201        // 1. Guaranteed by the caller
202        unsafe {
203            // @add-unsafe-context: strong_count
204            // @add-unsafe-context: RawReportRef
205            // @add-unsafe-context: ReportData
206            (self.strong_count)(ptr)
207        }
208    }
209
210    /// Returns a reference to the source of the error using the [`H::source`]
211    /// function used when creating this [`ReportVtable`].
212    ///
213    /// # Safety
214    ///
215    /// The caller must ensure:
216    ///
217    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
218    ///    the [`RawReportRef`].
219    ///
220    /// [`H::source`]: ContextHandler::source
221    #[inline]
222    pub(super) unsafe fn source<'a>(
223        &self,
224        ptr: RawReportRef<'a>,
225    ) -> Option<&'a (dyn core::error::Error + 'static)> {
226        // SAFETY: We know that `self.source` points to the function `source::<C, H>`
227        // below. That function's safety requirements are upheld:
228        // 1. Guaranteed by the caller
229        unsafe {
230            // @add-unsafe-context: source
231            // @add-unsafe-context: RawReportRef
232            // @add-unsafe-context: ReportData
233            (self.source)(ptr)
234        }
235    }
236
237    /// Formats the report using the [`H::display`] function
238    /// used when creating this [`ReportVtable`].
239    ///
240    /// [`H::display`]: ContextHandler::display
241    ///
242    /// # Safety
243    ///
244    /// The caller must ensure:
245    ///
246    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
247    ///    the [`RawReportRef`].
248    #[inline]
249    pub(super) unsafe fn display(
250        &self,
251        ptr: RawReportRef<'_>,
252        formatter: &mut core::fmt::Formatter<'_>,
253    ) -> core::fmt::Result {
254        // SAFETY: We know that `self.display` points to the function `display::<C, H>`
255        // below. That function's safety requirements are upheld:
256        // 1. Guaranteed by the caller
257        unsafe {
258            // @add-unsafe-context: display
259            // @add-unsafe-context: RawReportRef
260            // @add-unsafe-context: ReportData
261            (self.display)(ptr, formatter)
262        }
263    }
264
265    /// Formats the given `RawReportRef` using the [`H::debug`] function
266    /// used when creating this [`ReportVtable`].
267    ///
268    /// [`H::debug`]: ContextHandler::debug
269    ///
270    /// # Safety
271    ///
272    /// The caller must ensure:
273    ///
274    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
275    ///    the [`RawReportRef`].
276    #[inline]
277    pub(super) unsafe fn debug(
278        &self,
279        ptr: RawReportRef<'_>,
280        formatter: &mut core::fmt::Formatter<'_>,
281    ) -> core::fmt::Result {
282        // SAFETY: We know that `self.debug` points to the function `debug::<C, H>`
283        // below. That function's safety requirements are upheld:
284        // 1. Guaranteed by the caller
285        unsafe {
286            // @add-unsafe-context: debug
287            // @add-unsafe-context: RawReportRef
288            // @add-unsafe-context: ReportData
289            (self.debug)(ptr, formatter)
290        }
291    }
292
293    /// Calls the [`H::preferred_formatting_style`] function to get the
294    /// formatting style preferred by the context when formatted as part of
295    /// a report.
296    ///
297    /// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
298    ///
299    /// # Safety
300    ///
301    /// The caller must ensure:
302    ///
303    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
304    ///    the [`RawReportRef`].
305    #[inline]
306    pub(super) unsafe fn preferred_context_formatting_style(
307        &self,
308        ptr: RawReportRef<'_>,
309        report_formatting_function: FormattingFunction,
310    ) -> ContextFormattingStyle {
311        // SAFETY: We know that `self.preferred_context_formatting_style` points to the
312        // function `preferred_context_formatting_style::<C, H>` below.
313        // That function's safety requirements are upheld:
314        // 1. Guaranteed by the caller
315        unsafe {
316            // @add-unsafe-context: preferred_context_formatting_style
317            (self.preferred_context_formatting_style)(ptr, report_formatting_function)
318        }
319    }
320
321    /// Returns a `&dyn Any` reference to the context using the function pointer
322    /// stored in this [`ReportVtable`].
323    ///
324    /// # Safety
325    ///
326    /// The caller must ensure:
327    ///
328    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
329    ///    the [`RawReportRef`].
330    #[inline]
331    pub(super) unsafe fn context_as_any<'a>(
332        &self,
333        ptr: RawReportRef<'a>,
334    ) -> &'a (dyn Any + 'static) {
335        // SAFETY: We know that `self.context_as_any` points to the function
336        // `context_as_any::<C>` below. That function's safety requirements are
337        // upheld:
338        // 1. Guaranteed by the caller
339        unsafe {
340            // @add-unsafe-context: context_as_any
341            // @add-unsafe-context: RawReportRef
342            // @add-unsafe-context: ReportData
343            (self.context_as_any)(ptr)
344        }
345    }
346
347    /// Returns a `&mut dyn Any` reference to the context using the function
348    /// pointer stored in this [`ReportVtable`].
349    ///
350    /// # Safety
351    ///
352    /// The caller must ensure:
353    ///
354    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
355    ///    the [`RawReportMut`].
356    #[inline]
357    pub(super) unsafe fn context_as_any_mut<'a>(
358        &self,
359        ptr: RawReportMut<'a>,
360    ) -> &'a mut (dyn Any + 'static) {
361        // SAFETY: We know that `self.context_as_any_mut` points to the function
362        // `context_as_any_mut::<C>` below. That function's safety requirements
363        // are upheld:
364        // 1. Guaranteed by the caller
365        unsafe {
366            // @add-unsafe-context: context_as_any_mut
367            // @add-unsafe-context: RawReportMut
368            // @add-unsafe-context: ReportData
369            (self.context_as_any_mut)(ptr)
370        }
371    }
372}
373
374/// Drops the [`triomphe::Arc<ReportData<C>>`] instance pointed to by this
375/// pointer.
376///
377/// # Safety
378///
379/// The caller must ensure:
380///
381/// 1. The pointer comes from [`triomphe::Arc<ReportData<C>>`] via
382///    [`triomphe::Arc::into_raw`]
383/// 2. The context type `C` matches the actual context type stored in the
384///    [`ReportData`]
385/// 3. This method drops the [`triomphe::Arc<ReportData<C>>`], so the caller
386///    must ensure that the pointer has not previously been dropped, that it is
387///    able to transfer ownership of the pointer, and that it will not use the
388///    pointer after calling this method.
389pub(super) unsafe fn drop<C: 'static>(ptr: NonNull<ReportData<Erased>>) {
390    let ptr: NonNull<ReportData<C>> = ptr.cast();
391    let ptr = ptr.as_ptr();
392    // SAFETY:
393    // 1. The pointer has the correct type and came from `Arc::into_raw` (guaranteed
394    //    by caller)
395    // 2. After `from_raw`, the pointer is consumed and not accessed again
396    let arc = unsafe {
397        // @add-unsafe-context: ReportData
398        triomphe::Arc::from_raw(ptr)
399    };
400    core::mem::drop(arc);
401}
402
403/// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
404///
405/// # Safety
406///
407/// The caller must ensure:
408///
409/// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into a
410///    pointer via [`triomphe::Arc::into_raw`]
411/// 2. The pointer has full provenance over the `Arc` (i.e., it was not derived
412///    from a `&T` reference)
413/// 3. The context type `C` matches the actual context type stored in the
414///    [`ReportData`]
415/// 4. All other references to this report are compatible with shared ownership.
416///    Specifically none of them assume that the strong_count is `1`.
417unsafe fn clone_arc<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> RawReport {
418    let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
419
420    // SAFETY:
421    // - The pointer is valid and came from `Arc::into_raw` with the correct type
422    //   (guaranteed by the caller)
423    // - The pointer has full provenance over the `Arc` (i.e., it was not derived
424    //   from a `&T` reference) (guaranteed by the caller)
425    let arc_borrow = unsafe {
426        // @add-unsafe-context: ReportData
427        triomphe::ArcBorrow::from_ptr(ptr)
428    };
429
430    let arc = arc_borrow.clone_arc();
431    RawReport::from_arc(arc)
432}
433
434/// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to by
435/// this pointer.
436///
437/// # Safety
438///
439/// The caller must ensure:
440///
441/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
442unsafe fn strong_count<'a, C: 'static>(ptr: RawReportRef<'a>) -> usize {
443    let ptr: *const ReportData<C> = ptr.as_ptr().cast::<ReportData<C>>();
444
445    // SAFETY: The pointer is valid and came from `Arc::into_raw` with the correct
446    // type (guaranteed by the caller), which fulfills the requirements for
447    // `ArcBorrow::from_ptr`.
448    let arc_borrow = unsafe {
449        // @add-unsafe-context: ReportData
450        triomphe::ArcBorrow::from_ptr(ptr)
451    };
452
453    triomphe::ArcBorrow::strong_count(&arc_borrow)
454}
455
456/// Gets the source error from a report using its handler's source
457/// implementation.
458///
459/// # Safety
460///
461/// The caller must ensure:
462///
463/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
464unsafe fn source<'a, C: 'static, H: ContextHandler<C>>(
465    ptr: RawReportRef<'a>,
466) -> Option<&'a (dyn core::error::Error + 'static)> {
467    // SAFETY:
468    // 1. Guaranteed by the caller
469    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
470    H::source(context)
471}
472
473/// Formats a report using its handler's display implementation.
474///
475/// # Safety
476///
477/// The caller must ensure:
478///
479/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
480unsafe fn display<C: 'static, H: ContextHandler<C>>(
481    ptr: RawReportRef<'_>,
482    formatter: &mut core::fmt::Formatter<'_>,
483) -> core::fmt::Result {
484    // SAFETY:
485    // 1. Guaranteed by the caller
486    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
487    H::display(context, formatter)
488}
489
490/// Formats a report using its handler's debug implementation.
491///
492/// # Safety
493///
494/// The caller must ensure:
495///
496/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
497unsafe fn debug<C: 'static, H: ContextHandler<C>>(
498    ptr: RawReportRef<'_>,
499    formatter: &mut core::fmt::Formatter<'_>,
500) -> core::fmt::Result {
501    // SAFETY:
502    // 1. Guaranteed by the caller
503    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
504    H::debug(context, formatter)
505}
506
507/// Gets the preferred formatting style using the
508/// [`H::preferred_formatting_style`] function.
509///
510/// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
511///
512/// # Safety
513///
514/// The caller must ensure:
515///
516/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
517unsafe fn preferred_context_formatting_style<C: 'static, H: ContextHandler<C>>(
518    ptr: RawReportRef<'_>,
519    report_formatting_function: FormattingFunction,
520) -> ContextFormattingStyle {
521    // SAFETY:
522    // 1. Guaranteed by the caller
523    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
524    H::preferred_formatting_style(context, report_formatting_function)
525}
526
527/// Returns a `&dyn Any` view of the context.
528///
529/// # Safety
530///
531/// The caller must ensure:
532///
533/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
534unsafe fn context_as_any<'a, C: 'static>(ptr: RawReportRef<'a>) -> &'a (dyn Any + 'static) {
535    // SAFETY:
536    // 1. Guaranteed by the caller
537    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
538    context as &(dyn Any + 'static)
539}
540
541/// Returns a `&mut dyn Any` view of the context.
542///
543/// # Safety
544///
545/// The caller must ensure:
546///
547/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
548unsafe fn context_as_any_mut<'a, C: 'static>(ptr: RawReportMut<'a>) -> &'a mut (dyn Any + 'static) {
549    // SAFETY:
550    // 1. Guaranteed by the caller
551    let context: &mut C = unsafe { ptr.into_context_downcast_unchecked::<C>() };
552    context as &mut (dyn Any + 'static)
553}
554
555#[cfg(test)]
556mod tests {
557    use alloc::vec;
558    use core::{error::Error, fmt};
559
560    use super::*;
561    use crate::{handlers::ContextHandler, report::RawReport};
562
563    struct HandlerI32;
564    impl ContextHandler<i32> for HandlerI32 {
565        fn source(_value: &i32) -> Option<&(dyn Error + 'static)> {
566            None
567        }
568
569        fn display(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
570            fmt::Display::fmt(value, formatter)
571        }
572
573        fn debug(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
574            fmt::Debug::fmt(value, formatter)
575        }
576    }
577
578    #[test]
579    fn test_report_vtable_eq() {
580        // Test that vtables have proper static lifetime and can be safely shared
581        let vtable1 = ReportVtable::new::<i32, HandlerI32>();
582        let vtable2 = ReportVtable::new::<i32, HandlerI32>();
583
584        // Both should be the exact same static instance
585        assert!(core::ptr::eq(vtable1, vtable2));
586    }
587
588    #[test]
589    fn test_report_type_id() {
590        let vtable = ReportVtable::new::<i32, HandlerI32>();
591        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
592    }
593
594    #[test]
595    fn test_report_type_name() {
596        let vtable = ReportVtable::new::<i32, HandlerI32>();
597        assert_eq!(vtable.type_name(), core::any::type_name::<i32>());
598    }
599
600    #[test]
601    fn test_report_clone_eq() {
602        let report = RawReport::new::<_, HandlerI32>(42, vec![], vec![]);
603
604        // SAFETY: There are no assumptions about single ownership
605        let cloned_report = unsafe { report.as_ref().clone_arc() };
606
607        // Both reports should point to the same underlying data
608        assert!(core::ptr::eq(
609            report.as_ref().as_ptr(),
610            cloned_report.as_ref().as_ptr()
611        ));
612    }
613}