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