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