rootcause_internals/attachment/
vtable.rs

1//! Vtable for type-erased attachment operations.
2//!
3//! This module contains the [`AttachmentVtable`] which enables calling handler
4//! methods on attachments when their concrete attachment type `A` and handler
5//! type `H` have been erased. The vtable stores function pointers that dispatch
6//! to the correct typed implementations.
7//!
8//! This module encapsulates the fields of [`AttachmentVtable`] so they cannot
9//! be accessed directly. This visibility restriction guarantees the safety
10//! invariant: **the vtable's type parameters must match the actual attachment
11//! type and handler stored in the [`AttachmentData`]**.
12//!
13//! # Safety Invariant
14//!
15//! This invariant is maintained because vtables are created as `&'static`
16//! references via [`AttachmentVtable::new`], which pairs the function pointers
17//! with specific types `A` and `H` at compile time.
18
19use alloc::boxed::Box;
20use core::{any::TypeId, ptr::NonNull};
21
22use crate::{
23    attachment::{data::AttachmentData, raw::RawAttachmentRef},
24    handlers::{AttachmentFormattingStyle, AttachmentHandler, FormattingFunction},
25    util::Erased,
26};
27
28/// Vtable for type-erased attachment operations.
29///
30/// Contains function pointers for performing operations on attachments without
31/// knowing their concrete type at compile time.
32///
33/// # Safety Invariant
34///
35/// The fields `drop`, `display`, `debug`, and `preferred_formatting_style` are
36/// guaranteed to point to the functions defined below instantiated with the
37/// attachment type `A` and handler type `H` that were used to create this
38/// [`AttachmentVtable`].
39pub(crate) struct AttachmentVtable {
40    /// Gets the [`TypeId`] of the attachment type that was used to create this
41    /// [`AttachmentVtable`].
42    type_id: fn() -> TypeId,
43    /// Gets the [`TypeId`] of the handler that was used to create this
44    /// [`AttachmentVtable`].
45    handler_type_id: fn() -> TypeId,
46    /// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this
47    /// pointer.
48    drop: unsafe fn(NonNull<AttachmentData<Erased>>),
49    /// Formats the attachment using the `display` method on the handler.
50    display: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
51    /// Formats the attachment using the `debug` method on the handler.
52    debug: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
53    /// Get the formatting style preferred by the attachment when formatted as
54    /// part of a report.
55    preferred_formatting_style:
56        unsafe fn(RawAttachmentRef<'_>, FormattingFunction) -> AttachmentFormattingStyle,
57}
58
59impl AttachmentVtable {
60    /// Creates a new [`AttachmentVtable`] for the attachment type `A` and the
61    /// handler type `H`.
62    pub(super) const fn new<A: 'static, H: AttachmentHandler<A>>() -> &'static Self {
63        const {
64            &Self {
65                type_id: TypeId::of::<A>,
66                handler_type_id: TypeId::of::<H>,
67                drop: drop::<A>,
68                display: display::<A, H>,
69                debug: debug::<A, H>,
70                preferred_formatting_style: preferred_formatting_style::<A, H>,
71            }
72        }
73    }
74
75    /// Gets the [`TypeId`] of the attachment type that was used to create this
76    /// [`AttachmentVtable`].
77    #[inline]
78    pub(super) fn type_id(&self) -> TypeId {
79        (self.type_id)()
80    }
81
82    /// Gets the [`TypeId`] of the handler that was used to create this
83    /// [`AttachmentVtable`].
84    #[inline]
85    pub(super) fn handler_type_id(&self) -> TypeId {
86        (self.handler_type_id)()
87    }
88
89    /// Drops the `Box<AttachmentData<A>>` instance pointed to by this pointer.
90    ///
91    /// # Safety
92    ///
93    /// The caller must ensure:
94    ///
95    /// 1. The pointer comes from [`Box<AttachmentData<A>>`] via
96    ///    [`Box::into_raw`]
97    /// 2. This [`AttachmentVtable`] must be a vtable for the attachment type
98    ///    stored in the [`AttachmentData`].
99    /// 3. This method drops the [`Box<AttachmentData<A>>`], so the caller must
100    ///    ensure that the pointer has not previously been dropped, that it is
101    ///    able to transfer ownership of the pointer, and that it will not use
102    ///    the pointer after calling this method.
103    #[inline]
104    pub(super) unsafe fn drop(&self, ptr: NonNull<AttachmentData<Erased>>) {
105        // SAFETY: We know that `self.drop` points to the function `drop::<A>` below.
106        // That function's safety requirements are upheld:
107        // 1. Guaranteed by the caller
108        // 2. Guaranteed by the caller
109        // 3. Guaranteed by the caller
110        unsafe {
111            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
112            // @add-unsafe-context: drop
113            (self.drop)(ptr);
114        }
115    }
116
117    /// Formats the attachment using the [`H::display`] function
118    /// used when creating this [`AttachmentVtable`].
119    ///
120    /// [`H::display`]: AttachmentHandler::display
121    ///
122    /// # Safety
123    ///
124    /// The caller must ensure:
125    ///
126    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
127    ///    stored in the [`RawAttachmentRef`].
128    #[inline]
129    pub(super) unsafe fn display(
130        &self,
131        ptr: RawAttachmentRef<'_>,
132        formatter: &mut core::fmt::Formatter<'_>,
133    ) -> core::fmt::Result {
134        // SAFETY: We know that the `self.display` field points to the function
135        // `display::<A, H>` below. That function's safety requirements are upheld:
136        // 1. Guaranteed by the caller
137        unsafe {
138            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
139            // @add-unsafe-context: display
140            (self.display)(ptr, formatter)
141        }
142    }
143
144    /// Formats the attachment using the [`H::debug`] function
145    /// used when creating this [`AttachmentVtable`].
146    ///
147    /// [`H::debug`]: AttachmentHandler::debug
148    ///
149    /// # Safety
150    ///
151    /// The caller must ensure:
152    ///
153    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
154    ///    stored in the [`RawAttachmentRef`].
155    #[inline]
156    pub(super) unsafe fn debug(
157        &self,
158        ptr: RawAttachmentRef<'_>,
159        formatter: &mut core::fmt::Formatter<'_>,
160    ) -> core::fmt::Result {
161        // SAFETY: We know that the `self.debug` field points to the function
162        // `debug::<A, H>` below. That function's safety requirements are upheld:
163        // 1. Guaranteed by the caller
164        unsafe {
165            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
166            // @add-unsafe-context: debug
167            (self.debug)(ptr, formatter)
168        }
169    }
170
171    /// Gets the preferred formatting style using the
172    /// [`H::preferred_formatting_style`] function used when creating this
173    /// [`AttachmentVtable`].
174    ///
175    /// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
176    ///
177    /// # Safety
178    ///
179    /// The caller must ensure:
180    ///
181    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
182    ///    stored in the [`RawAttachmentRef`].
183    #[inline]
184    pub(super) unsafe fn preferred_formatting_style(
185        &self,
186        ptr: RawAttachmentRef<'_>,
187        report_formatting_function: FormattingFunction,
188    ) -> AttachmentFormattingStyle {
189        // SAFETY: We know that the `self.preferred_formatting_style` field points to
190        // the function `preferred_formatting_style::<A, H>` below. That
191        // function's safety requirements are upheld:
192        // 1. Guaranteed by the caller
193        unsafe {
194            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
195            // @add-unsafe-context: preferred_formatting_style
196            (self.preferred_formatting_style)(ptr, report_formatting_function)
197        }
198    }
199}
200
201/// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this pointer.
202///
203/// # Safety
204///
205/// The caller must ensure:
206///
207/// 1. The pointer comes from [`Box<AttachmentData<A>>`] via [`Box::into_raw`]
208/// 2. The attachment type `A` matches the actual attachment type stored in the
209///    [`AttachmentData`]
210/// 3. This method drops the [`Box<AttachmentData<A>>`], so the caller must
211///    ensure that the pointer has not previously been dropped, that it is able
212///    to transfer ownership of the pointer, and that it will not use the
213///    pointer after calling this method.
214unsafe fn drop<A: 'static>(ptr: NonNull<AttachmentData<Erased>>) {
215    let ptr: NonNull<AttachmentData<A>> = ptr.cast();
216    let ptr = ptr.as_ptr();
217    // SAFETY: Our pointer has the correct type as guaranteed by the caller, and it
218    // came from a call to `Box::into_raw` as also guaranteed by our caller.
219    let boxed = unsafe {
220        // @add-unsafe-context: AttachmentData
221        Box::from_raw(ptr)
222    };
223    core::mem::drop(boxed);
224}
225
226/// Formats an attachment using its handler's display implementation.
227///
228/// # Safety
229///
230/// The caller must ensure:
231///
232/// 1. The type `A` matches the actual attachment type stored in the
233///    [`AttachmentData`]
234unsafe fn display<A: 'static, H: AttachmentHandler<A>>(
235    ptr: RawAttachmentRef<'_>,
236    formatter: &mut core::fmt::Formatter<'_>,
237) -> core::fmt::Result {
238    // SAFETY:
239    // 1. Guaranteed by the caller
240    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
241    H::display(attachment, formatter)
242}
243
244/// Formats an attachment using its handler's debug implementation.
245///
246/// # Safety
247///
248/// The caller must ensure:
249///
250/// 1. The type `A` matches the actual attachment type stored in the
251///    [`AttachmentData`]
252unsafe fn debug<A: 'static, H: AttachmentHandler<A>>(
253    ptr: RawAttachmentRef<'_>,
254    formatter: &mut core::fmt::Formatter<'_>,
255) -> core::fmt::Result {
256    // SAFETY:
257    // 1. Guaranteed by the caller
258    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
259    H::debug(attachment, formatter)
260}
261
262/// Gets the preferred formatting style using the
263/// [`H::preferred_formatting_style`] function.
264///
265/// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
266///
267/// # Safety
268///
269/// The caller must ensure:
270///
271/// 1. The type `A` matches the actual attachment type stored in the
272///    [`AttachmentData`]
273unsafe fn preferred_formatting_style<A: 'static, H: AttachmentHandler<A>>(
274    ptr: RawAttachmentRef<'_>,
275    report_formatting_function: FormattingFunction,
276) -> AttachmentFormattingStyle {
277    // SAFETY:
278    // 1. Guaranteed by the caller
279    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
280    H::preferred_formatting_style(attachment, report_formatting_function)
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286    use crate::handlers::AttachmentHandler;
287
288    struct HandlerI32;
289    impl AttachmentHandler<i32> for HandlerI32 {
290        fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
291            core::fmt::Display::fmt(value, formatter)
292        }
293
294        fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
295            core::fmt::Debug::fmt(value, formatter)
296        }
297    }
298
299    #[test]
300    fn test_attachment_vtable_eq() {
301        // Test that vtables have proper static lifetime and can be safely shared
302        let vtable1 = AttachmentVtable::new::<i32, HandlerI32>();
303        let vtable2 = AttachmentVtable::new::<i32, HandlerI32>();
304
305        // Both should be the exact same static instance
306        assert!(core::ptr::eq(vtable1, vtable2));
307    }
308
309    #[test]
310    fn test_attachment_type_id() {
311        let vtable = AttachmentVtable::new::<i32, HandlerI32>();
312        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
313    }
314}