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