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 (self.drop)(ptr);
145 }
146 }
147
148 /// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
149 ///
150 /// # Safety
151 ///
152 /// The caller must ensure:
153 ///
154 /// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into
155 /// a pointer via [`triomphe::Arc::into_raw`]
156 /// 2. The pointer has full provenance over the `Arc` (i.e., it was not
157 /// derived from a `&T` reference)
158 /// 3. This [`ReportVtable`] must be a vtable for the context type stored in
159 /// the [`ReportData`].
160 /// 4. All other references to this report are compatible with shared
161 /// ownership. Specifically none of them assume that the strong_count is
162 /// `1`.
163 #[inline]
164 pub(super) unsafe fn clone_arc(&self, ptr: NonNull<ReportData<Erased>>) -> RawReport {
165 // SAFETY: We know that `self.clone_arc` points to the function `clone_arc::<C>`
166 // below. That function's safety requirements are upheld:
167 // 1. Guaranteed by the caller
168 // 2. Guaranteed by the caller
169 // 3. Guaranteed by the caller
170 // 4. Guaranteed by the caller
171 unsafe {
172 // @add-unsafe-context: clone_arc
173 (self.clone_arc)(ptr)
174 }
175 }
176
177 /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
178 /// by this pointer.
179 ///
180 /// # Safety
181 ///
182 /// The caller must ensure:
183 ///
184 /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
185 /// the [`RawReportRef`].
186 #[inline]
187 pub(super) unsafe fn strong_count<'a>(&self, ptr: RawReportRef<'a>) -> usize {
188 // SAFETY: We know that `self.strong_count` points to the function
189 // `strong_count::<C>` below. That function's safety requirements are
190 // upheld:
191 // 1. Guaranteed by the caller
192 unsafe {
193 // @add-unsafe-context: strong_count
194 (self.strong_count)(ptr)
195 }
196 }
197
198 /// Returns a reference to the source of the error using the [`H::source`]
199 /// function used when creating this [`ReportVtable`].
200 ///
201 /// # Safety
202 ///
203 /// The caller must ensure:
204 ///
205 /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
206 /// the [`RawReportRef`].
207 ///
208 /// [`H::source`]: ContextHandler::source
209 #[inline]
210 pub(super) unsafe fn source<'a>(
211 &self,
212 ptr: RawReportRef<'a>,
213 ) -> Option<&'a (dyn core::error::Error + 'static)> {
214 // SAFETY: We know that `self.source` points to the function `source::<C, H>`
215 // below. That function's safety requirements are upheld:
216 // 1. Guaranteed by the caller
217 unsafe {
218 // @add-unsafe-context: source
219 (self.source)(ptr)
220 }
221 }
222
223 /// Formats the report using the [`H::display`] function
224 /// used when creating this [`ReportVtable`].
225 ///
226 /// [`H::display`]: ContextHandler::display
227 ///
228 /// # Safety
229 ///
230 /// The caller must ensure:
231 ///
232 /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
233 /// the [`RawReportRef`].
234 #[inline]
235 pub(super) unsafe fn display(
236 &self,
237 ptr: RawReportRef<'_>,
238 formatter: &mut core::fmt::Formatter<'_>,
239 ) -> core::fmt::Result {
240 // SAFETY: We know that `self.display` points to the function `display::<C, H>`
241 // below. That function's safety requirements are upheld:
242 // 1. Guaranteed by the caller
243 unsafe {
244 // @add-unsafe-context: display
245 (self.display)(ptr, formatter)
246 }
247 }
248
249 /// Formats the given `RawReportRef` using the [`H::debug`] function
250 /// used when creating this [`ReportVtable`].
251 ///
252 /// [`H::debug`]: ContextHandler::debug
253 ///
254 /// # Safety
255 ///
256 /// The caller must ensure:
257 ///
258 /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
259 /// the [`RawReportRef`].
260 #[inline]
261 pub(super) unsafe fn debug(
262 &self,
263 ptr: RawReportRef<'_>,
264 formatter: &mut core::fmt::Formatter<'_>,
265 ) -> core::fmt::Result {
266 // SAFETY: We know that `self.debug` points to the function `debug::<C, H>`
267 // below. That function's safety requirements are upheld:
268 // 1. Guaranteed by the caller
269 unsafe {
270 // @add-unsafe-context: debug
271 (self.debug)(ptr, formatter)
272 }
273 }
274
275 /// Calls the [`H::preferred_formatting_style`] function to get the
276 /// formatting style preferred by the context when formatted as part of
277 /// a report.
278 ///
279 /// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
280 ///
281 /// # Safety
282 ///
283 /// The caller must ensure:
284 ///
285 /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
286 /// the [`RawReportRef`].
287 #[inline]
288 pub(super) unsafe fn preferred_context_formatting_style(
289 &self,
290 ptr: RawReportRef<'_>,
291 report_formatting_function: FormattingFunction,
292 ) -> ContextFormattingStyle {
293 // SAFETY: We know that `self.preferred_context_formatting_style` points to the
294 // function `preferred_context_formatting_style::<C, H>` below.
295 // That function's safety requirements are upheld:
296 // 1. Guaranteed by the caller
297 unsafe {
298 // @add-unsafe-context: preferred_context_formatting_style
299 (self.preferred_context_formatting_style)(ptr, report_formatting_function)
300 }
301 }
302}
303
304/// Drops the [`triomphe::Arc<ReportData<C>>`] instance pointed to by this
305/// pointer.
306///
307/// # Safety
308///
309/// The caller must ensure:
310///
311/// 1. The pointer comes from [`triomphe::Arc<ReportData<C>>`] via
312/// [`triomphe::Arc::into_raw`]
313/// 2. The context type `C` matches the actual context type stored in the
314/// [`ReportData`]
315/// 3. This method drops the [`triomphe::Arc<ReportData<C>>`], so the caller
316/// must ensure that the pointer has not previously been dropped, that it is
317/// able to transfer ownership of the pointer, and that it will not use the
318/// pointer after calling this method.
319pub(super) unsafe fn drop<C: 'static>(ptr: NonNull<ReportData<Erased>>) {
320 let ptr: NonNull<ReportData<C>> = ptr.cast();
321 let ptr = ptr.as_ptr();
322 // SAFETY:
323 // 1. The pointer has the correct type and came from `Arc::into_raw` (guaranteed
324 // by caller)
325 // 2. After `from_raw`, the pointer is consumed and not accessed again
326 let arc = unsafe {
327 // @add-unsafe-context: ReportData
328 triomphe::Arc::from_raw(ptr)
329 };
330 core::mem::drop(arc);
331}
332
333/// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
334///
335/// # Safety
336///
337/// The caller must ensure:
338///
339/// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into a
340/// pointer via [`triomphe::Arc::into_raw`]
341/// 2. The pointer has full provenance over the `Arc` (i.e., it was not derived
342/// from a `&T` reference)
343/// 3. The context type `C` matches the actual context type stored in the
344/// [`ReportData`]
345/// 4. All other references to this report are compatible with shared ownership.
346/// Specifically none of them assume that the strong_count is `1`.
347unsafe fn clone_arc<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> RawReport {
348 let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
349
350 // SAFETY:
351 // - The pointer is valid and came from `Arc::into_raw` with the correct type
352 // (guaranteed by the caller)
353 // - The pointer has full provenance over the `Arc` (i.e., it was not derived
354 // from a `&T` reference) (guaranteed by the caller)
355 let arc_borrow = unsafe {
356 // @add-unsafe-context: ReportData
357 triomphe::ArcBorrow::from_ptr(ptr)
358 };
359
360 let arc = arc_borrow.clone_arc();
361 RawReport::from_arc(arc)
362}
363
364/// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to by
365/// this pointer.
366///
367/// # Safety
368///
369/// The caller must ensure:
370///
371/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
372unsafe fn strong_count<'a, C: 'static>(ptr: RawReportRef<'a>) -> usize {
373 let ptr: *const ReportData<C> = ptr.as_ptr().cast::<ReportData<C>>();
374
375 // SAFETY: The pointer is valid and came from `Arc::into_raw` with the correct
376 // type (guaranteed by the caller), which fulfills the requirements for
377 // `ArcBorrow::from_ptr`.
378 let arc_borrow = unsafe {
379 // @add-unsafe-context: ReportData
380 triomphe::ArcBorrow::from_ptr(ptr)
381 };
382
383 triomphe::ArcBorrow::strong_count(&arc_borrow)
384}
385
386/// Gets the source error from a report using its handler's source
387/// implementation.
388///
389/// # Safety
390///
391/// The caller must ensure:
392///
393/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
394unsafe fn source<'a, C: 'static, H: ContextHandler<C>>(
395 ptr: RawReportRef<'a>,
396) -> Option<&'a (dyn core::error::Error + 'static)> {
397 // SAFETY:
398 // 1. Guaranteed by the caller
399 let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
400 H::source(context)
401}
402
403/// Formats a report using its handler's display implementation.
404///
405/// # Safety
406///
407/// The caller must ensure:
408///
409/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
410unsafe fn display<C: 'static, H: ContextHandler<C>>(
411 ptr: RawReportRef<'_>,
412 formatter: &mut core::fmt::Formatter<'_>,
413) -> core::fmt::Result {
414 // SAFETY:
415 // 1. Guaranteed by the caller
416 let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
417 H::display(context, formatter)
418}
419
420/// Formats a report using its handler's debug implementation.
421///
422/// # Safety
423///
424/// The caller must ensure:
425///
426/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
427unsafe fn debug<C: 'static, H: ContextHandler<C>>(
428 ptr: RawReportRef<'_>,
429 formatter: &mut core::fmt::Formatter<'_>,
430) -> core::fmt::Result {
431 // SAFETY:
432 // 1. Guaranteed by the caller
433 let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
434 H::debug(context, formatter)
435}
436
437/// Gets the preferred formatting style using the
438/// [`H::preferred_formatting_style`] function.
439///
440/// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
441///
442/// # Safety
443///
444/// The caller must ensure:
445///
446/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
447unsafe fn preferred_context_formatting_style<C: 'static, H: ContextHandler<C>>(
448 ptr: RawReportRef<'_>,
449 report_formatting_function: FormattingFunction,
450) -> ContextFormattingStyle {
451 // SAFETY:
452 // 1. Guaranteed by the caller
453 let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
454 H::preferred_formatting_style(context, report_formatting_function)
455}
456
457#[cfg(test)]
458mod tests {
459 use alloc::vec;
460 use core::{error::Error, fmt};
461
462 use super::*;
463 use crate::{handlers::ContextHandler, report::RawReport};
464
465 struct HandlerI32;
466 impl ContextHandler<i32> for HandlerI32 {
467 fn source(_value: &i32) -> Option<&(dyn Error + 'static)> {
468 None
469 }
470
471 fn display(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
472 fmt::Display::fmt(value, formatter)
473 }
474
475 fn debug(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
476 fmt::Debug::fmt(value, formatter)
477 }
478 }
479
480 #[test]
481 fn test_report_vtable_eq() {
482 // Test that vtables have proper static lifetime and can be safely shared
483 let vtable1 = ReportVtable::new::<i32, HandlerI32>();
484 let vtable2 = ReportVtable::new::<i32, HandlerI32>();
485
486 // Both should be the exact same static instance
487 assert!(core::ptr::eq(vtable1, vtable2));
488 }
489
490 #[test]
491 fn test_report_type_id() {
492 let vtable = ReportVtable::new::<i32, HandlerI32>();
493 assert_eq!(vtable.type_id(), TypeId::of::<i32>());
494 }
495
496 #[test]
497 fn test_report_type_name() {
498 let vtable = ReportVtable::new::<i32, HandlerI32>();
499 assert_eq!(vtable.type_name(), core::any::type_name::<i32>());
500 }
501
502 #[test]
503 fn test_report_clone_eq() {
504 let report = RawReport::new::<_, HandlerI32>(42, vec![], vec![]);
505
506 // SAFETY: There are no assumptions about single ownership
507 let cloned_report = unsafe { report.as_ref().clone_arc() };
508
509 // Both reports should point to the same underlying data
510 assert!(core::ptr::eq(
511 report.as_ref().as_ptr(),
512 cloned_report.as_ref().as_ptr()
513 ));
514 }
515}