weaveffi/abi/object.rs
1//! Reference-counted interface objects: the producer half of the ABI's object
2//! model.
3//!
4//! An interface value crosses the C ABI as a `{tag}*` that is really an
5//! [`Arc<T>`] turned into a raw pointer with [`Arc::into_raw`]. The count
6//! lives inside the producer's allocation, so every consumer wrapper, every
7//! value buffer holding an object token, and every in-flight async call can
8//! own its own strong reference: `{tag}_clone` bumps the count and
9//! `{tag}_destroy` releases one reference. The object is dropped when the last
10//! reference goes.
11//!
12//! The `#[weaveffi::module]` expansion calls these helpers from its generated
13//! thunks; producers never touch them directly. They exist so every `unsafe`
14//! pointer-to-`Arc` conversion has one audited home.
15
16use std::sync::Arc;
17
18/// Hand one strong reference to the consumer.
19///
20/// Accepts either an owned `T` (wrapped in a fresh [`Arc`]) or an existing
21/// `Arc<T>` (whose reference is transferred), so a constructor returning
22/// `Self` and a method returning `Arc<Self>` lower through the same call.
23/// The consumer releases the reference with `{tag}_destroy`.
24#[must_use]
25pub fn lower_object<T>(value: impl Into<Arc<T>>) -> *mut T {
26 crate::abi::leak::track(crate::abi::leak::OBJECTS, 1);
27 Arc::into_raw(value.into()).cast_mut()
28}
29
30/// Lower an optional object: `None` becomes null, `Some` transfers one strong
31/// reference exactly like [`lower_object`].
32#[must_use]
33pub fn lower_object_opt<T>(value: Option<impl Into<Arc<T>>>) -> *mut T {
34 match value {
35 Some(v) => lower_object(v),
36 None => std::ptr::null_mut(),
37 }
38}
39
40/// Borrow an object for the duration of a call without touching its count.
41/// Null yields `None`.
42///
43/// # Safety
44///
45/// `ptr` must be null or a pointer produced by [`lower_object`] (or
46/// `{tag}_clone`) whose reference the caller keeps alive for the chosen
47/// lifetime `'a`. Generated thunks bound `'a` by the call, matching the ABI
48/// contract that object parameters are borrowed for the call's duration.
49#[must_use]
50pub unsafe fn object_ref<'a, T>(ptr: *const T) -> Option<&'a T> {
51 if ptr.is_null() {
52 None
53 } else {
54 // SAFETY: the caller guarantees `ptr` is a live `Arc<T>` allocation
55 // for `'a`.
56 Some(unsafe { &*ptr })
57 }
58}
59
60/// Take a new strong reference to a borrowed object, so the producer can
61/// retain it past the call. Null yields `None`.
62///
63/// # Safety
64///
65/// `ptr` must be null or a pointer produced by [`lower_object`] (or
66/// `{tag}_clone`) whose reference is still live.
67#[must_use]
68pub unsafe fn object_arc<T>(ptr: *const T) -> Option<Arc<T>> {
69 if ptr.is_null() {
70 return None;
71 }
72 // SAFETY: the caller guarantees `ptr` came from `Arc::into_raw` and is
73 // live, so bumping the count and rebuilding an `Arc` is sound.
74 unsafe {
75 Arc::increment_strong_count(ptr);
76 Some(Arc::from_raw(ptr))
77 }
78}
79
80/// The body of every `{tag}_clone` symbol: return a new strong reference to
81/// the same object. The pointer value is unchanged; null is a no-op returning
82/// null.
83///
84/// # Safety
85///
86/// `ptr` must be null or a live pointer produced by [`lower_object`].
87#[must_use]
88pub unsafe fn object_clone<T>(ptr: *const T) -> *mut T {
89 if ptr.is_null() {
90 return std::ptr::null_mut();
91 }
92 // SAFETY: as documented on the function.
93 unsafe { Arc::increment_strong_count(ptr) };
94 crate::abi::leak::track(crate::abi::leak::OBJECTS, 1);
95 ptr.cast_mut()
96}
97
98/// The body of every `{tag}_destroy` symbol: release one strong reference.
99/// The object is dropped when the last reference goes. Null is a no-op, and
100/// a panicking `Drop` is swallowed because a destructor has no `out_err`
101/// slot and must never unwind into C.
102///
103/// # Safety
104///
105/// `ptr` must be null or a live pointer produced by [`lower_object`] whose
106/// reference the caller owns and will not use again.
107pub unsafe fn object_destroy<T>(ptr: *mut T) {
108 if ptr.is_null() {
109 return;
110 }
111 crate::abi::leak::track(crate::abi::leak::OBJECTS, -1);
112 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
113 // SAFETY: as documented on the function.
114 unsafe { Arc::decrement_strong_count(ptr) };
115 }));
116}
117
118/// The object token an interface value takes inside a value buffer: the
119/// [`lower_object`] pointer widened to `u64`. The token carries one strong
120/// reference; whoever decodes it adopts that reference.
121#[must_use]
122pub fn object_to_token<T>(value: &Arc<T>) -> u64 {
123 crate::abi::leak::track(crate::abi::leak::OBJECTS, 1);
124 Arc::into_raw(Arc::clone(value)) as usize as u64
125}
126
127/// Adopt the strong reference carried by an object token read from a value
128/// buffer. A zero token is a contract violation and yields `None`.
129///
130/// # Safety
131///
132/// `token` must be zero or a token written by [`object_to_token`] (or by a
133/// consumer that cloned the object before encoding) for a `T` allocation,
134/// exactly once: adopting the same token twice double-frees.
135#[must_use]
136pub unsafe fn object_from_token<T>(token: u64) -> Option<Arc<T>> {
137 let ptr = token as usize as *const T;
138 if ptr.is_null() {
139 return None;
140 }
141 crate::abi::leak::track(crate::abi::leak::OBJECTS, -1);
142 // SAFETY: the caller guarantees the token carries one live strong
143 // reference that has not been adopted yet.
144 Some(unsafe { Arc::from_raw(ptr) })
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use std::sync::atomic::{AtomicUsize, Ordering};
151
152 struct Probe<'a>(&'a AtomicUsize);
153 impl Drop for Probe<'_> {
154 fn drop(&mut self) {
155 self.0.fetch_add(1, Ordering::SeqCst);
156 }
157 }
158
159 #[test]
160 fn clone_and_destroy_track_the_count() {
161 let drops = AtomicUsize::new(0);
162 let ptr = lower_object(Probe(&drops));
163 let again = unsafe { object_clone(ptr) };
164 assert_eq!(ptr, again);
165 unsafe { object_destroy(ptr) };
166 assert_eq!(drops.load(Ordering::SeqCst), 0);
167 unsafe { object_destroy(again) };
168 assert_eq!(drops.load(Ordering::SeqCst), 1);
169 }
170
171 #[test]
172 fn borrowing_does_not_change_the_count() {
173 let drops = AtomicUsize::new(0);
174 let ptr: *mut Probe<'_> = lower_object(Arc::new(Probe(&drops)));
175 {
176 let r = unsafe { object_ref(ptr) }.unwrap();
177 assert!(std::ptr::eq(r, ptr));
178 }
179 let retained = unsafe { object_arc(ptr) }.unwrap();
180 unsafe { object_destroy(ptr) };
181 assert_eq!(drops.load(Ordering::SeqCst), 0);
182 drop(retained);
183 assert_eq!(drops.load(Ordering::SeqCst), 1);
184 }
185
186 #[test]
187 fn tokens_carry_one_reference() {
188 let drops = AtomicUsize::new(0);
189 let arc = Arc::new(Probe(&drops));
190 let token = object_to_token(&arc);
191 assert_eq!(Arc::strong_count(&arc), 2);
192 let back: Arc<Probe<'_>> = unsafe { object_from_token(token) }.unwrap();
193 assert!(Arc::ptr_eq(&arc, &back));
194 drop(back);
195 assert_eq!(Arc::strong_count(&arc), 1);
196 assert!(unsafe { object_from_token::<Probe<'_>>(0) }.is_none());
197 }
198
199 #[test]
200 fn null_is_a_no_op_everywhere() {
201 assert!(unsafe { object_ref::<u8>(std::ptr::null()) }.is_none());
202 assert!(unsafe { object_arc::<u8>(std::ptr::null()) }.is_none());
203 assert!(unsafe { object_clone::<u8>(std::ptr::null()) }.is_null());
204 unsafe { object_destroy::<u8>(std::ptr::null_mut()) };
205 assert!(lower_object_opt::<u8>(None::<u8>).is_null());
206 }
207}