cairo/
user_data.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::marker::PhantomData;
4
5use crate::ffi;
6
7pub struct UserDataKey<T> {
8    pub(crate) ffi: ffi::cairo_user_data_key_t,
9    marker: PhantomData<*const T>,
10}
11
12unsafe impl<T> Sync for UserDataKey<T> {}
13
14impl<T> UserDataKey<T> {
15    pub const fn new() -> Self {
16        Self {
17            ffi: ffi::cairo_user_data_key_t { unused: 0 },
18            marker: PhantomData,
19        }
20    }
21}
22
23impl<T> Default for UserDataKey<T> {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29// In a safe API for user data we can’t make `get_user_data`
30// transfer full ownership of the value to the caller (e.g. by returning `Box<T>`)
31// because `self` still has a pointer to that value
32// and `get_user_data` could be called again with the same key.
33//
34// We also can’t return a `&T` reference that borrows from `self`
35// because the value could be removed with `remove_user_data` or replaced with `set_user_data`
36// while the borrow still needs to be valid.
37// (Borrowing with `&mut self` would not help as `Self` can be itself reference-counted.)
38//
39// Therefore, the value must be reference-counted.
40//
41// We use `Rc` over `Arc` because the types implementing these methods are `!Send` and `!Sync`.
42// See <https://github.com/gtk-rs/cairo/issues/256>
43
44macro_rules! user_data_methods {
45    ($ffi_get_user_data: path, $ffi_set_user_data: path,) => {
46        /// Attach user data to `self` for the given `key`.
47        pub fn set_user_data<T: 'static>(
48            &self,
49            key: &'static crate::UserDataKey<T>,
50            value: std::rc::Rc<T>,
51        ) -> Result<(), crate::Error> {
52            unsafe extern "C" fn destructor<T>(ptr: *mut libc::c_void) {
53                unsafe {
54                    let ptr: *const T = ptr as _;
55                    drop(std::rc::Rc::from_raw(ptr))
56                }
57            }
58            // Safety:
59            //
60            // The destructor’s cast and `from_raw` are symmetric
61            // with the `into_raw` and cast below.
62            // They both transfer ownership of one strong reference:
63            // neither of them touches the reference count.
64            let ptr: *const T = std::rc::Rc::into_raw(value);
65            let ptr = ptr as *mut T as *mut libc::c_void;
66            let status = crate::utils::status_to_result(unsafe {
67                $ffi_set_user_data(self.to_raw_none(), &key.ffi, ptr, Some(destructor::<T>))
68            });
69
70            if status.is_err() {
71                // Safety:
72                //
73                // On errors the user data is leaked by cairo and needs to be freed here.
74                unsafe {
75                    destructor::<T>(ptr);
76                }
77            }
78
79            status
80        }
81
82        /// Return the user data previously attached to `self` with the given `key`, if any.
83        pub fn user_data<T: 'static>(
84            &self,
85            key: &'static crate::UserDataKey<T>,
86        ) -> Option<std::rc::Rc<T>> {
87            let ptr = self.user_data_ptr(key)?.as_ptr();
88
89            // Safety:
90            //
91            // `Rc::from_raw` would normally take ownership of a strong reference for this pointer.
92            // But `self` still has a copy of that pointer and `get_user_data` can be called again
93            // with the same key.
94            // We use `ManuallyDrop` to avoid running the destructor of that first `Rc`,
95            // and return a cloned one (which increments the reference count).
96            unsafe {
97                let rc = std::mem::ManuallyDrop::new(std::rc::Rc::from_raw(ptr));
98                Some(std::rc::Rc::clone(&rc))
99            }
100        }
101
102        /// Return the user data previously attached to `self` with the given `key`, if any,
103        /// without incrementing the reference count.
104        ///
105        /// The pointer is valid when it is returned from this method,
106        /// until the cairo object that `self` represents is destroyed
107        /// or `remove_user_data` or `set_user_data` is called with the same key.
108        pub fn user_data_ptr<T: 'static>(
109            &self,
110            key: &'static crate::UserDataKey<T>,
111        ) -> Option<std::ptr::NonNull<T>> {
112            // Safety:
113            //
114            // If `ffi_get_user_data` returns a non-null pointer,
115            // there was a previous call to `ffi_set_user_data` with a key with the same address.
116            // Either:
117            //
118            // * This was a call to a Rust `Self::set_user_data` method.
119            //   Because that method takes a `&'static` reference,
120            //   the key used then must live at that address until the end of the process.
121            //   Because `UserDataKey<T>` has a non-zero size regardless of `T`,
122            //   no other `UserDataKey<U>` value can have the same address.
123            //   Therefore, the `T` type was the same then at it is now and `cast` is type-safe.
124            //
125            // * Or, it is technically possible that the `set` call was to the C function directly,
126            //   with a `cairo_user_data_key_t` in heap-allocated memory that was then freed,
127            //   then `Box::new(UserDataKey::new()).leak()` was used to create a `&'static`
128            //   that happens to have the same address because the allocator for `Box`
129            //   reused that memory region.
130            //   Since this involves a C (or FFI) call *and* is so far out of “typical” use
131            //   of the user data functionality, we consider this a misuse of an unsafe API.
132            unsafe {
133                let ptr = $ffi_get_user_data(self.to_raw_none(), &key.ffi);
134                Some(std::ptr::NonNull::new(ptr)?.cast())
135            }
136        }
137
138        /// Unattached from `self` the user data associated with `key`, if any.
139        /// If there is no other `Rc` strong reference, the data is destroyed.
140        pub fn remove_user_data<T: 'static>(
141            &self,
142            key: &'static crate::UserDataKey<T>,
143        ) -> Result<(), crate::Error> {
144            let status = unsafe {
145                $ffi_set_user_data(self.to_raw_none(), &key.ffi, std::ptr::null_mut(), None)
146            };
147            crate::utils::status_to_result(status)
148        }
149    };
150}