Skip to main content

fbl/
ref_ptr.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7use crate::recyclable::{Recyclable, UninitRecyclable};
8use crate::ref_counted::{HasRefCount, HasRefCountUpgradeable};
9use core::mem::MaybeUninit;
10use core::ops::Deref;
11use core::ptr::NonNull;
12use kalloc::AllocError;
13
14use pin_init::{Init, PinInit};
15
16/// `RefPtr<T>` holds a reference to an intrusively-refcounted object of type
17/// T that deletes the object when the refcount drops to 0.
18///
19/// T should be a struct that contains a `fbl::RefCounted` field and implements
20/// `HasRefCount` and `Destroy` traits.
21#[repr(transparent)]
22pub struct RefPtr<T>
23where
24    T: HasRefCount + Recyclable,
25{
26    ptr: NonNull<T>,
27}
28
29impl<T: HasRefCount + Recyclable> RefPtr<T> {
30    /// Constructs a `RefPtr` from a raw pointer that has already been adopted.
31    ///
32    /// # Safety
33    ///
34    /// - The caller must ensure that `ptr` is valid and has a ref count already
35    ///   acquired.
36    /// - `ptr` must have been allocated in such a way that calling `T::recycle(ptr)` is a
37    ///   correct way to deallocate the pointer.
38    pub unsafe fn from_raw(ptr: *const T) -> Self {
39        // SAFETY: The caller must ensure that ptr is valid.
40        unsafe { RefPtr { ptr: NonNull::new_unchecked(ptr as *mut T) } }
41    }
42
43    /// Constructs a `RefPtr` from a raw pointer that has already been adopted, unless the pointer
44    /// is null.
45    ///
46    /// # Safety
47    ///
48    /// The caller must ensure that `ptr` is either null or, if not null that:
49    /// - a ref count already acquired.
50    /// - has been allocated in such a way that calling `T::recycle(ptr)` is a correct way to
51    ///   deallocate the pointer.
52    pub unsafe fn try_from_raw(ptr: *const T) -> Option<Self> {
53        NonNull::new(ptr as *mut T).map(|ptr| RefPtr { ptr })
54    }
55
56    /// Helper function that allocates a new instance of `T` using `T::allocate` and
57    /// returns a `RefPtr` wrapping it.
58    ///
59    /// This is an internal helper function that should not be used directly.
60    /// Use the `make_ref_counted!(...)` macro instead of this function to properly
61    /// initialize the ref count.
62    ///
63    /// # Safety
64    ///
65    /// The caller must ensure that `T` has a RefCounted field that is not
66    /// already adopted.
67    pub unsafe fn try_new(value: T) -> Result<RefPtr<T>, AllocError> {
68        let mut ptr = T::allocate(value)?;
69        // SAFETY: The caller must ensure that T has a RefCounted field that is not
70        // already adopted.
71        unsafe { ptr.as_mut().ref_count().adopt() };
72        Ok(RefPtr { ptr })
73    }
74
75    /// Returns the raw pointer to the object.
76    pub fn as_ptr(this: &Self) -> *const T {
77        this.ptr.as_ptr()
78    }
79
80    /// Returns `true` if the two `RefPtr`s point to the same object.
81    pub fn ptr_eq(a: &Self, b: &Self) -> bool {
82        a.ptr == b.ptr
83    }
84
85    /// Consume the `RefPtr` and return the raw pointer without modifying the ref count.
86    ///
87    /// The caller is responsible for maintaining the reference count.
88    pub fn into_raw(this: Self) -> *const T {
89        let ptr = this.ptr;
90        core::mem::forget(this);
91        ptr.as_ptr()
92    }
93
94    /// Casts this `RefPtr` to point to a different type.
95    ///
96    /// # Safety
97    ///
98    /// The caller must ensure that the object pointed to by this `RefPtr` can be safely
99    /// treated as an instance of type `U`.
100    pub unsafe fn cast<U>(self) -> RefPtr<U>
101    where
102        U: HasRefCount + Recyclable,
103    {
104        let ptr = self.ptr.cast::<U>();
105        core::mem::forget(self);
106        RefPtr { ptr }
107    }
108
109    /// Use the given pin-initializer to pin-initialize a `T` inside of a new `RefPtr`.
110    pub fn try_pin_init<E>(init: impl PinInit<T, E>) -> Result<Self, E>
111    where
112        T: UninitRecyclable,
113        E: From<AllocError>,
114    {
115        let ptr = T::allocate_uninit()?;
116        let guard = UninitRefGuard { ptr };
117        let slot = guard.ptr.as_ptr() as *mut T;
118        // SAFETY: `slot` is valid and will not be moved.
119        unsafe { init.__pinned_init(slot)? };
120        // SAFETY: The object is now initialized, so we can access its ref_count.
121        unsafe { (*slot).ref_count().adopt() };
122        let initialized_ptr = guard.ptr.cast::<T>();
123        core::mem::forget(guard);
124        let initialized_ref = RefPtr { ptr: initialized_ptr };
125        Ok(initialized_ref)
126    }
127
128    /// Use the given initializer to in-place initialize a `T` inside of a new `RefPtr`.
129    pub fn try_init<E>(init: impl Init<T, E>) -> Result<Self, E>
130    where
131        T: UninitRecyclable,
132        E: From<AllocError>,
133    {
134        let ptr = T::allocate_uninit()?;
135        let guard = UninitRefGuard { ptr };
136        let slot = guard.ptr.as_ptr() as *mut T;
137        // SAFETY: `slot` is valid.
138        unsafe { init.__init(slot)? };
139        // SAFETY: The object is now initialized, so we can access its ref_count.
140        unsafe { (*slot).ref_count().adopt() };
141        let initialized_ptr = guard.ptr.cast::<T>();
142        core::mem::forget(guard);
143        Ok(RefPtr { ptr: initialized_ptr })
144    }
145
146    /// Use the given pin-initializer to pin-initialize a `T` inside of a new `RefPtr`.
147    #[inline]
148    pub fn pin_init(init: impl PinInit<T, core::convert::Infallible>) -> Result<Self, AllocError>
149    where
150        T: UninitRecyclable,
151    {
152        let init = unsafe {
153            ::pin_init::pin_init_from_closure(|slot| {
154                init.__pinned_init(slot).map_err(|i| match i {})
155            })
156        };
157        Self::try_pin_init(init)
158    }
159
160    /// Use the given initializer to in-place initialize a `T` inside of a new `RefPtr`.
161    #[inline]
162    pub fn init(init: impl Init<T, core::convert::Infallible>) -> Result<Self, AllocError>
163    where
164        T: UninitRecyclable,
165    {
166        let init = unsafe {
167            ::pin_init::init_from_closure(|slot| init.__init(slot).map_err(|i| match i {}))
168        };
169        Self::try_init(init)
170    }
171
172    /// Manually increments the reference count of `target`.
173    ///
174    /// Every call to `add_ref` should be balanced by a subsequent drop of a
175    /// `RefPtr` (e.g. constructed via `RefPtr::from_raw`), otherwise memory will
176    /// be leaked. It is safe to leak memory in Rust.
177    #[inline]
178    pub fn add_ref(target: &T) {
179        target.ref_count().add_ref();
180    }
181
182    /// Constructs a `RefPtr` from a reference by incrementing its ref count.
183    #[inline]
184    pub fn from_ref(target: &T) -> Self {
185        target.ref_count().add_ref();
186        RefPtr { ptr: NonNull::from(target) }
187    }
188}
189
190impl<T: HasRefCount + Recyclable + HasRefCountUpgradeable> RefPtr<T> {
191    /// Constructs a RefPtr from a raw T* which is being held alive by RefPtr
192    /// with the caveat that the existing RefPtr might be in the process of
193    /// destructing the T object. When the T object is in the destructor, the
194    /// resulting RefPtr is null, otherwise the resulting RefPtr points to T*
195    /// with the updated reference count.
196    ///
197    /// The only way for this to be a valid pattern is that the call is made
198    /// while holding some lock and that the same lock also is used to protect the
199    /// value of T* .
200    ///
201    /// This pattern is needed in collaborating objects which cannot hold a
202    /// RefPtr to each other because it would cause a reference cycle. Instead
203    /// there is a raw pointer from one to the other and a RefPtr in the
204    /// other direction. When needed the raw pointer can be upgraded via
205    /// make_ref_ptr_upgrade_from_raw and operated outside the lock.
206    ///
207    /// # Safety
208    ///
209    /// - The caller must ensure that `ptr` is valid.
210    /// - `ptr` must have been allocated in such a way that calling `T::recycle(ptr)` is a
211    ///   correct way to deallocate the pointer.
212    /// - Caller must hold a lock or otherwise know that `ptr` lives for the duration of this
213    ///   method.
214    pub unsafe fn make_ref_ptr_upgrade_from_raw(ptr: *const T) -> Option<Self> {
215        // SAFETY: The caller must ensure that ptr is valid.
216        unsafe {
217            if !ptr.as_ref_unchecked().ref_count().add_ref_maybe_in_destructor() {
218                None
219            } else {
220                Some(Self::from_raw(ptr))
221            }
222        }
223    }
224}
225
226impl<T: HasRefCount + Recyclable> Deref for RefPtr<T> {
227    type Target = T;
228    #[inline]
229    fn deref(&self) -> &Self::Target {
230        unsafe { self.ptr.as_ref() }
231    }
232}
233
234impl<T: HasRefCount + Recyclable> Clone for RefPtr<T> {
235    #[inline]
236    fn clone(&self) -> Self {
237        self.deref().ref_count().add_ref();
238        RefPtr { ptr: self.ptr }
239    }
240}
241
242impl<T: HasRefCount + Recyclable> Drop for RefPtr<T> {
243    #[inline]
244    fn drop(&mut self) {
245        if self.deref().ref_count().release() {
246            unsafe {
247                T::recycle(self.ptr);
248            }
249        }
250    }
251}
252
253impl<T: HasRefCount + Recyclable> PartialEq for RefPtr<T> {
254    #[inline]
255    fn eq(&self, other: &Self) -> bool {
256        RefPtr::ptr_eq(self, other)
257    }
258}
259
260impl<T: HasRefCount + Recyclable> Eq for RefPtr<T> {}
261
262unsafe impl<T: HasRefCount + Recyclable + Send + Sync> Send for RefPtr<T> {}
263unsafe impl<T: HasRefCount + Recyclable + Send + Sync> Sync for RefPtr<T> {}
264
265struct UninitRefGuard<T: UninitRecyclable> {
266    ptr: NonNull<MaybeUninit<T>>,
267}
268
269impl<T: UninitRecyclable> Drop for UninitRefGuard<T> {
270    fn drop(&mut self) {
271        unsafe {
272            T::recycle_uninit(self.ptr);
273        }
274    }
275}
276
277/// Macro to construct a RefPtr, automatically populating the ref_count field.
278#[macro_export]
279macro_rules! make_ref_counted {
280    ($ty:ident { $($field:ident : $val:expr),* $(,)? }) => {
281        // SAFETY: The macro creates a new object with a ref count of 1.
282        unsafe {
283            $crate::RefPtr::try_new($ty {
284                ref_count: $crate::RefCounted::new(),
285                __fbl_ref_counted_guard: (),
286                $($field : $val),*
287            })
288        }
289    };
290}
291
292/// Macro to construct a RefPtr with pin-initialization, automatically populating the ref_count
293/// field.
294#[macro_export]
295macro_rules! pin_make_ref_counted {
296    ($ty:ident { $($field:tt)* }) => {
297        $crate::RefPtr::pin_init($crate::pin_init::pin_init!($ty {
298            ref_count: $crate::RefCounted::new(),
299            __fbl_ref_counted_guard: (),
300            $($field)*
301        }))
302    };
303}
304
305/// Macro to construct a RefPtr with fallible pin-initialization, automatically populating the
306/// ref_count field.
307#[macro_export]
308macro_rules! try_pin_make_ref_counted {
309    ($ty:ident { $($field:tt)* }) => {
310        $crate::RefPtr::try_pin_init($crate::pin_init::pin_init!($ty {
311            ref_count: $crate::RefCounted::new(),
312            __fbl_ref_counted_guard: (),
313            $($field)*
314        }))
315    };
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    use core::ffi::c_void;
322    use core::pin::Pin;
323    use core::ptr::null;
324    use core::sync::atomic::{AtomicBool, Ordering};
325
326    extern crate alloc;
327    extern crate std;
328    use alloc::sync::Arc;
329    use std::sync::{Barrier, Mutex};
330    use std::thread;
331
332    #[unsafe(no_mangle)]
333    pub extern "C" fn rust_recycle_test_rust_ref_counted(ptr: *mut c_void) {
334        unsafe { TestRustRefCounted::recycle_ffi(ptr) }
335    }
336
337    unsafe extern "C" {
338        fn test_import_rust_ref_counted(ptr: *mut c_void);
339    }
340
341    #[fbl::ref_counted]
342    #[pin_init::pin_data(PinnedDrop)]
343    #[derive(crate::Recyclable)]
344    #[repr(C)]
345    pub struct TestRustRefCounted {
346        destroyed: Arc<AtomicBool>,
347    }
348
349    ::zr::static_assert!(core::mem::size_of::<RefPtr<TestRustRefCounted>>() == 8);
350    ::zr::static_assert!(core::mem::align_of::<RefPtr<TestRustRefCounted>>() == 8);
351    ::zr::static_assert!(core::mem::size_of::<Option<RefPtr<TestRustRefCounted>>>() == 8);
352    ::zr::static_assert!(core::mem::align_of::<Option<RefPtr<TestRustRefCounted>>>() == 8);
353
354    #[pin_init::pinned_drop]
355    impl pin_init::PinnedDrop for TestRustRefCounted {
356        fn drop(self: Pin<&mut Self>) {
357            self.destroyed.store(true, Ordering::Relaxed);
358        }
359    }
360
361    #[test]
362    fn test_rust_drops_reference() {
363        let destroyed = Arc::new(AtomicBool::new(false));
364        {
365            let ref_ptr =
366                make_ref_counted!(TestRustRefCounted { destroyed: destroyed.clone() }).unwrap();
367            assert!(!destroyed.load(Ordering::Relaxed));
368            let ref_ptr_clone = ref_ptr.clone();
369            drop(ref_ptr_clone);
370            assert!(!destroyed.load(Ordering::Relaxed));
371        } // Drop ref_ptr -> count becomes 0 -> calls destroy -> triggers Drop trait!
372
373        assert!(destroyed.load(Ordering::Relaxed));
374    }
375
376    #[test]
377    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
378    fn test_cpp_drops_reference() {
379        let destroyed = Arc::new(AtomicBool::new(false));
380        let ref_ptr =
381            make_ref_counted!(TestRustRefCounted { destroyed: destroyed.clone() }).unwrap();
382        let raw_ptr = RefPtr::into_raw(ref_ptr);
383
384        unsafe {
385            assert!(!destroyed.load(Ordering::Relaxed));
386            // Pass to C++!
387            test_import_rust_ref_counted(raw_ptr as *const TestRustRefCounted as *mut c_void);
388            // C++ should have acquired reference and released it!
389            // And since count was 1, it should have dropped it!
390            assert!(destroyed.load(Ordering::Relaxed));
391        }
392    }
393
394    #[test]
395    fn test_ref_ptr_compare() {
396        let destroyed1 = Arc::new(AtomicBool::new(false));
397        let destroyed2 = Arc::new(AtomicBool::new(false));
398        let ptr1 = make_ref_counted!(TestRustRefCounted { destroyed: destroyed1.clone() }).unwrap();
399        let ptr2 = make_ref_counted!(TestRustRefCounted { destroyed: destroyed2.clone() }).unwrap();
400        let ptr1_clone = ptr1.clone();
401
402        assert!(ptr1 == ptr1);
403        assert!(ptr1 != ptr2);
404        assert!(ptr1 == ptr1_clone);
405    }
406
407    #[test]
408    fn test_rust_pin_init() {
409        let destroyed = Arc::new(AtomicBool::new(false));
410        let destroyed_clone = destroyed.clone();
411        {
412            let ref_ptr =
413                pin_make_ref_counted!(TestRustRefCounted { destroyed: destroyed_clone }).unwrap();
414            assert!(!destroyed.load(Ordering::Relaxed));
415            let ref_ptr_clone = ref_ptr.clone();
416            drop(ref_ptr_clone);
417            assert!(!destroyed.load(Ordering::Relaxed));
418        } // Drop ref_ptr
419        assert!(destroyed.load(Ordering::Relaxed));
420    }
421
422    #[fbl::ref_counted]
423    #[pin_init::pin_data]
424    #[derive(crate::Recyclable)]
425    #[repr(C)]
426    struct FallibleInit {
427        value: i32,
428    }
429
430    #[test]
431    fn test_rust_try_pin_init_fail() {
432        let init = unsafe {
433            ::pin_init::pin_init_from_closure(
434                |_slot: *mut FallibleInit| -> Result<(), AllocError> { Err(AllocError) },
435            )
436        };
437        let res = RefPtr::try_pin_init(init);
438        assert!(res.is_err());
439    }
440
441    #[test]
442    fn test_null_try_from() {
443        let maybe_ref_ptr = unsafe { RefPtr::try_from_raw(null::<TestRustRefCounted>()) };
444        assert!(maybe_ref_ptr.is_none());
445    }
446
447    #[test]
448    fn test_add_ref() {
449        let destroyed = Arc::new(AtomicBool::new(false));
450        {
451            let ref_ptr =
452                make_ref_counted!(TestRustRefCounted { destroyed: destroyed.clone() }).unwrap();
453            assert!(!destroyed.load(Ordering::Relaxed));
454            RefPtr::add_ref(&*ref_ptr);
455            let ref_ptr2 = unsafe { RefPtr::from_raw(RefPtr::as_ptr(&ref_ptr)) };
456            drop(ref_ptr);
457            assert!(!destroyed.load(Ordering::Relaxed));
458            drop(ref_ptr2);
459            assert!(destroyed.load(Ordering::Relaxed));
460        }
461    }
462
463    #[test]
464    fn test_from_ref() {
465        let destroyed = Arc::new(AtomicBool::new(false));
466        {
467            let ref_ptr =
468                make_ref_counted!(TestRustRefCounted { destroyed: destroyed.clone() }).unwrap();
469            assert!(!destroyed.load(Ordering::Relaxed));
470            let ref_ptr2 = RefPtr::from_ref(&*ref_ptr);
471            assert!(ref_ptr == ref_ptr2);
472            drop(ref_ptr);
473            assert!(!destroyed.load(Ordering::Relaxed));
474            drop(ref_ptr2);
475            assert!(destroyed.load(Ordering::Relaxed));
476        }
477    }
478
479    #[fbl::ref_counted]
480    #[derive(crate::Recyclable)]
481    #[repr(C)]
482    struct RawUpgradeTester {
483        mutex: Arc<Mutex<()>>,
484        destroying: Arc<AtomicBool>,
485        destroying_barrier: Option<Arc<Barrier>>,
486    }
487
488    impl HasRefCountUpgradeable for RawUpgradeTester {}
489
490    impl Drop for RawUpgradeTester {
491        fn drop(&mut self) {
492            self.destroying.store(true, Ordering::SeqCst);
493            if let Some(barrier) = &self.destroying_barrier {
494                barrier.wait();
495            }
496            let _guard = self.mutex.lock().unwrap();
497        }
498    }
499
500    #[test]
501    fn test_upgrade_fail() {
502        let mutex = Arc::new(Mutex::new(()));
503        let destroying = Arc::new(AtomicBool::new(false));
504        let destroying_barrier = Arc::new(Barrier::new(2));
505
506        let ref_ptr = make_ref_counted!(RawUpgradeTester {
507            mutex: mutex.clone(),
508            destroying: destroying.clone(),
509            destroying_barrier: Some(destroying_barrier.clone()),
510        })
511        .unwrap();
512        let raw = RefPtr::as_ptr(&ref_ptr);
513
514        let handle = {
515            let _guard = mutex.lock().unwrap();
516            let handle = thread::spawn(move || {
517                // Dropping `ref_ptr` will call the destructor, which we expect to
518                // block because `test_upgrade_fail` is holding the mutex.
519                drop(ref_ptr);
520            });
521
522            // Wait until the thread is in the destructor.
523            destroying_barrier.wait();
524            assert!(destroying.load(Ordering::SeqCst));
525
526            // The RawUpgradeTester must be blocked in the destructor, the upgrade will fail.
527            // SAFETY: `raw` is valid because the destructor is blocked on `mutex`.
528            let upgrade1 = unsafe { RefPtr::make_ref_ptr_upgrade_from_raw(raw) };
529            assert!(upgrade1.is_none());
530
531            // Verify that the previous upgrade attempt did not change the refcount.
532            // SAFETY: `raw` is valid because the destructor is blocked on `mutex`.
533            let upgrade2 = unsafe { RefPtr::make_ref_ptr_upgrade_from_raw(raw) };
534            assert!(upgrade2.is_none());
535
536            handle
537        };
538
539        handle.join().unwrap();
540    }
541
542    #[test]
543    fn test_upgrade_success() {
544        let mutex = Arc::new(Mutex::new(()));
545        let destroying = Arc::new(AtomicBool::new(false));
546
547        let ref_ptr = make_ref_counted!(RawUpgradeTester {
548            mutex: mutex.clone(),
549            destroying: destroying.clone(),
550            destroying_barrier: None,
551        })
552        .unwrap();
553        let raw = RefPtr::as_ptr(&ref_ptr);
554
555        {
556            let _guard = mutex.lock().unwrap();
557            // RawUpgradeTester is not in the destructor so the upgrade should
558            // succeed.
559            // SAFETY: `raw` is valid because `ref_ptr` is alive.
560            let upgrade = unsafe { RefPtr::make_ref_ptr_upgrade_from_raw(raw) };
561            assert!(upgrade.is_some());
562        }
563
564        drop(ref_ptr);
565        assert!(destroying.load(Ordering::SeqCst));
566    }
567}