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