Skip to main content

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}