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