Skip to main content

fbl/
opaque_ref_counted.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;
8use crate::ref_counted::{HasRefCount, RefCounted};
9use core::marker::{PhantomData, PhantomPinned};
10use core::ops::Deref;
11use core::ptr::NonNull;
12use kalloc::AllocError;
13use zr::Opaque;
14
15/// A wrapper for C++ objects that are known to use `fbl::RefCounted`
16/// and have their reference count at offset 0.
17#[repr(transparent)]
18pub struct OpaqueRefCounted<T>(Opaque<T>);
19
20impl<T> OpaqueRefCounted<T> {
21    /// Returns a raw pointer to the opaque data.
22    pub fn get(&self) -> *mut T {
23        self.0.get()
24    }
25}
26
27impl<T> HasRefCount for OpaqueRefCounted<T> {
28    fn ref_count(&self) -> &RefCounted {
29        // SAFETY: OpaqueRefCounted guarantees that the ref count is at offset 0.
30        unsafe { &*(self.get() as *const RefCounted) }
31    }
32}
33
34/// A zero-sized facade type for opaque ref-counted C++ objects that derive from a base class `B`.
35///
36/// This is used as a field in Rust facade structs that represent C++ objects of unknown size.
37/// It keeps the facade struct `Sized` (size 0) so it can be used in FFI (thin pointers) and with
38/// generic containers like `RefPtr`, while providing `Send`, `Sync`, and `PhantomPinned`.
39#[repr(C)]
40#[derive(Default)]
41pub struct OpaqueRefCountedFacade<B = RefCounted> {
42    _marker: PhantomData<(PhantomPinned, fn() -> B)>,
43    _facade: zr::OpaqueFacade,
44}
45
46unsafe impl<B> Send for OpaqueRefCountedFacade<B> {}
47unsafe impl<B> Sync for OpaqueRefCountedFacade<B> {}
48
49impl<B: HasRefCount> HasRefCount for OpaqueRefCountedFacade<B> {
50    fn ref_count(&self) -> &RefCounted {
51        // SAFETY: OpaqueRefCountedFacade<B> is at offset 0 of the facade struct.
52        unsafe {
53            let b_ptr = self as *const Self as *const B;
54            (*b_ptr).ref_count()
55        }
56    }
57}
58
59unsafe impl<B: Recyclable> Recyclable for OpaqueRefCountedFacade<B> {
60    unsafe fn recycle(ptr: NonNull<Self>) {
61        unsafe {
62            B::recycle(ptr.cast::<B>());
63        }
64    }
65
66    fn allocate(_value: Self) -> Result<NonNull<Self>, AllocError> {
67        Err(AllocError)
68    }
69}
70
71/// Trait for facade types that wrap an `OpaqueRefCountedFacade<B>` and derefer to `B`.
72///
73/// Implementing this trait automatically provides `HasRefCount` and `Recyclable` for `Self`.
74///
75/// # Safety
76///
77/// `Self` must be a facade struct for a C++ object that inherits from `TargetBase` and derefers to `TargetBase`.
78pub unsafe trait IsOpaqueRefCounted: Deref + Sized {
79    type TargetBase: HasRefCount + Recyclable;
80}
81
82impl<T: IsOpaqueRefCounted> HasRefCount for T {
83    fn ref_count(&self) -> &RefCounted {
84        let base_ptr = self.deref() as *const T::Target as *const T::TargetBase;
85        // SAFETY: T is a facade struct for a C++ object that inherits from T::TargetBase.
86        unsafe { (*base_ptr).ref_count() }
87    }
88}
89
90unsafe impl<T: IsOpaqueRefCounted> Recyclable for T {
91    unsafe fn recycle(ptr: NonNull<Self>) {
92        unsafe {
93            let base_ptr = ptr.cast::<T::TargetBase>();
94            <T::TargetBase as Recyclable>::recycle(base_ptr);
95        }
96    }
97
98    fn allocate(_value: Self) -> Result<NonNull<Self>, AllocError> {
99        Err(AllocError)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use crate::recyclable::Recyclable;
107    use crate::ref_ptr::RefPtr;
108    use core::ffi::c_void;
109    use core::ptr::NonNull;
110
111    unsafe extern "C" {
112        fn create_cpp_ref_counted_object(destroyed: *mut bool) -> *mut c_void;
113        fn destroy_cpp_ref_counted_object(ptr: *mut c_void);
114    }
115
116    pub struct TestCppRefCountedObject;
117
118    unsafe impl Recyclable for OpaqueRefCounted<TestCppRefCountedObject> {
119        unsafe fn recycle(ptr: NonNull<Self>) {
120            unsafe {
121                destroy_cpp_ref_counted_object(ptr.as_ptr() as *mut c_void);
122            }
123        }
124
125        fn allocate(_value: Self) -> Result<NonNull<Self>, ::kalloc::AllocError> {
126            Err(::kalloc::AllocError)
127        }
128    }
129
130    #[test]
131    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
132    fn test_cross_lang_ref_ptr() {
133        use core::sync::atomic::{AtomicBool, Ordering};
134
135        let destroyed = AtomicBool::new(false);
136        unsafe {
137            let raw_ptr = create_cpp_ref_counted_object(destroyed.as_ptr());
138            assert!(!destroyed.load(Ordering::Relaxed));
139
140            {
141                let ref_ptr =
142                    RefPtr::from_raw(raw_ptr as *mut OpaqueRefCounted<TestCppRefCountedObject>);
143                assert!(!destroyed.load(Ordering::Relaxed));
144
145                let ref_ptr_clone = ref_ptr.clone();
146                assert!(!destroyed.load(Ordering::Relaxed));
147
148                // Drop clone
149                drop(ref_ptr_clone);
150                assert!(!destroyed.load(Ordering::Relaxed));
151            } // Drop ref_ptr -> count becomes 0 -> calls recycle -> calls C++ release!
152
153            assert!(destroyed.load(Ordering::Relaxed));
154        }
155    }
156
157    #[test]
158    fn test_opaque_ref_counted_allocate_fails() {
159        let val = OpaqueRefCounted(Opaque::uninit());
160        let res = OpaqueRefCounted::<TestCppRefCountedObject>::allocate(val);
161        assert!(res.is_err());
162    }
163
164    pub struct TestCppFacadeBase;
165    unsafe impl Recyclable for TestCppFacadeBase {
166        unsafe fn recycle(ptr: NonNull<Self>) {
167            unsafe {
168                destroy_cpp_ref_counted_object(ptr.as_ptr() as *mut c_void);
169            }
170        }
171        fn allocate(_value: Self) -> Result<NonNull<Self>, ::kalloc::AllocError> {
172            Err(::kalloc::AllocError)
173        }
174    }
175    impl HasRefCount for TestCppFacadeBase {
176        fn ref_count(&self) -> &RefCounted {
177            unsafe { &*(self as *const Self as *const RefCounted) }
178        }
179    }
180
181    #[repr(C)]
182    pub struct TestSubtypeFacade {
183        _facade: OpaqueRefCountedFacade<TestCppFacadeBase>,
184    }
185    impl Deref for TestSubtypeFacade {
186        type Target = TestCppFacadeBase;
187        fn deref(&self) -> &Self::Target {
188            unsafe { &*(self as *const Self as *const TestCppFacadeBase) }
189        }
190    }
191    unsafe impl IsOpaqueRefCounted for TestSubtypeFacade {
192        type TargetBase = TestCppFacadeBase;
193    }
194
195    #[test]
196    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
197    fn test_facade_ref_ptr() {
198        use core::sync::atomic::{AtomicBool, Ordering};
199
200        let destroyed = AtomicBool::new(false);
201        unsafe {
202            let raw_ptr = create_cpp_ref_counted_object(destroyed.as_ptr());
203            assert!(!destroyed.load(Ordering::Relaxed));
204
205            {
206                let ref_ptr = RefPtr::from_raw(raw_ptr as *mut TestSubtypeFacade);
207                assert!(!destroyed.load(Ordering::Relaxed));
208
209                let ref_ptr_clone = ref_ptr.clone();
210                assert!(!destroyed.load(Ordering::Relaxed));
211
212                drop(ref_ptr_clone);
213                assert!(!destroyed.load(Ordering::Relaxed));
214            }
215
216            assert!(destroyed.load(Ordering::Relaxed));
217        }
218    }
219}