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 zr::Opaque;
13
14/// A wrapper for C++ objects that are known to use `fbl::RefCounted`
15/// and have their reference count at offset 0.
16#[repr(transparent)]
17pub struct OpaqueRefCounted<T>(Opaque<T>);
18
19impl<T> OpaqueRefCounted<T> {
20    /// Returns a raw pointer to the opaque data.
21    pub fn get(&self) -> *mut T {
22        self.0.get()
23    }
24}
25
26impl<T> HasRefCount for OpaqueRefCounted<T> {
27    fn ref_count(&self) -> &RefCounted {
28        // SAFETY: OpaqueRefCounted guarantees that the ref count is at offset 0.
29        unsafe { &*(self.get() as *const RefCounted) }
30    }
31}
32
33/// A zero-sized facade type for opaque ref-counted C++ objects that derive from a base class `B`.
34///
35/// This is used as a field in Rust facade structs that represent C++ objects of unknown size.
36/// It keeps the facade struct `Sized` (size 0) so it can be used in FFI (thin pointers) and with
37/// generic containers like `RefPtr`, while providing `Send`, `Sync`, and `PhantomPinned`.
38#[repr(C)]
39#[derive(Default)]
40pub struct OpaqueRefCountedFacade<B = RefCounted> {
41    _marker: PhantomData<(PhantomPinned, fn() -> B)>,
42    _facade: zr::OpaqueFacade,
43}
44
45unsafe impl<B> Send for OpaqueRefCountedFacade<B> {}
46unsafe impl<B> Sync for OpaqueRefCountedFacade<B> {}
47
48impl<B: HasRefCount> HasRefCount for OpaqueRefCountedFacade<B> {
49    fn ref_count(&self) -> &RefCounted {
50        // SAFETY: OpaqueRefCountedFacade<B> is at offset 0 of the facade struct.
51        unsafe {
52            let b_ptr = self as *const Self as *const B;
53            (*b_ptr).ref_count()
54        }
55    }
56}
57
58unsafe impl<B: Recyclable> Recyclable for OpaqueRefCountedFacade<B> {
59    unsafe fn recycle(ptr: NonNull<Self>) {
60        unsafe {
61            B::recycle(ptr.cast::<B>());
62        }
63    }
64}
65
66/// Trait for facade types that wrap an `OpaqueRefCountedFacade<B>` and derefer to `B`.
67///
68/// Implementing this trait automatically provides `HasRefCount` and `Recyclable` for `Self`.
69///
70/// # Safety
71///
72/// `Self` must be a facade struct for a C++ object that inherits from `TargetBase` and derefers to
73/// `TargetBase`.
74pub unsafe trait IsOpaqueRefCounted: Deref + Sized {
75    type TargetBase: HasRefCount + Recyclable;
76}
77
78impl<T: IsOpaqueRefCounted> HasRefCount for T {
79    fn ref_count(&self) -> &RefCounted {
80        let base_ptr = self.deref() as *const T::Target as *const T::TargetBase;
81        // SAFETY: T is a facade struct for a C++ object that inherits from T::TargetBase.
82        unsafe { (*base_ptr).ref_count() }
83    }
84}
85
86unsafe impl<T: IsOpaqueRefCounted> Recyclable for T {
87    unsafe fn recycle(ptr: NonNull<Self>) {
88        unsafe {
89            let base_ptr = ptr.cast::<T::TargetBase>();
90            <T::TargetBase as Recyclable>::recycle(base_ptr);
91        }
92    }
93}
94
95/// Declares a zero-sized facade struct for an opaque refcounted C++ object and implements
96/// `HasRefCount` and `Recyclable` for it.
97///
98/// Supported forms:
99/// 1. Where `fbl::RefCounted` is at offset 0 of the C++ object:
100/// ```rust
101/// fbl::impl_opaque_ref_counted_facade!(
102///     /// Facade type representing the C++ `iommu::Iommu` object.
103///     pub struct Iommu,
104///     cpp_iommu_recycle,
105/// );
106/// ```
107/// 2. Where `fbl::RefCounted` is at a non-zero offset or requires a C++ helper function:
108/// ```rust
109/// fbl::impl_opaque_ref_counted_facade!(
110///     /// Facade type representing the C++ `VmObject` object.
111///     pub struct VmObject,
112///     cpp_vm_object_free,
113///     cpp_vm_object_get_ref_counted,
114/// );
115/// ```
116#[macro_export]
117macro_rules! impl_opaque_ref_counted_facade {
118    (
119        $(#[$meta:meta])*
120        $vis:vis struct $name:ident,
121        $recycle_fn:path $(,)?
122    ) => {
123        $(#[$meta])*
124        #[repr(C)]
125        $vis struct $name {
126            _facade: $crate::OpaqueRefCountedFacade,
127        }
128
129        impl $crate::HasRefCount for $name {
130            fn ref_count(&self) -> &$crate::RefCounted {
131                // SAFETY: `$name` represents a C++ `fbl::RefCounted` object whose ref count is at
132                // offset 0.
133                unsafe { &*(self as *const Self as *const $crate::RefCounted) }
134            }
135        }
136
137        // SAFETY: `$name` represents a C++ `fbl::RefCounted` object.
138        unsafe impl $crate::Recyclable for $name {
139            unsafe fn recycle(ptr: core::ptr::NonNull<Self>) {
140                // SAFETY: `ptr` was constructed from `RefPtr::into_raw` on a valid `$name` facade.
141                unsafe {
142                    $recycle_fn(ptr.as_ptr() as *mut Self);
143                }
144            }
145        }
146    };
147    (
148        $(#[$meta:meta])*
149        $vis:vis struct $name:ident,
150        $recycle_fn:path,
151        $get_ref_counted_fn:path $(,)?
152    ) => {
153        $(#[$meta])*
154        #[repr(C)]
155        $vis struct $name {
156            _facade: $crate::OpaqueRefCountedFacade,
157        }
158
159        impl $crate::HasRefCount for $name {
160            fn ref_count(&self) -> &$crate::RefCounted {
161                // SAFETY: `$get_ref_counted_fn` returns a valid pointer to the C++
162                // `fbl::RefCounted` subobject of `$name`.
163                unsafe {
164                    &*($get_ref_counted_fn(self as *const Self as *mut Self)
165                        as *const $crate::RefCounted)
166                }
167            }
168        }
169
170        // SAFETY: `$name` represents a C++ `fbl::RefCounted` object.
171        unsafe impl $crate::Recyclable for $name {
172            unsafe fn recycle(ptr: core::ptr::NonNull<Self>) {
173                // SAFETY: `ptr` was constructed from `RefPtr::into_raw` on a valid `$name` facade.
174                unsafe {
175                    $recycle_fn(ptr.as_ptr() as *mut Self);
176                }
177            }
178        }
179    };
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use crate::recyclable::Recyclable;
186    use crate::ref_ptr::RefPtr;
187    use core::ffi::c_void;
188    use core::ptr::NonNull;
189
190    unsafe extern "C" {
191        fn create_cpp_ref_counted_object(destroyed: *mut bool) -> *mut c_void;
192        fn destroy_cpp_ref_counted_object(ptr: *mut c_void);
193    }
194
195    pub struct TestCppRefCountedObject;
196
197    unsafe impl Recyclable for OpaqueRefCounted<TestCppRefCountedObject> {
198        unsafe fn recycle(ptr: NonNull<Self>) {
199            unsafe {
200                destroy_cpp_ref_counted_object(ptr.as_ptr() as *mut c_void);
201            }
202        }
203    }
204
205    #[test]
206    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
207    fn test_cross_lang_ref_ptr() {
208        use core::sync::atomic::{AtomicBool, Ordering};
209
210        let destroyed = AtomicBool::new(false);
211        unsafe {
212            let raw_ptr = create_cpp_ref_counted_object(destroyed.as_ptr());
213            assert!(!destroyed.load(Ordering::Relaxed));
214
215            {
216                let ref_ptr =
217                    RefPtr::from_raw(raw_ptr as *mut OpaqueRefCounted<TestCppRefCountedObject>);
218                assert!(!destroyed.load(Ordering::Relaxed));
219
220                let ref_ptr_clone = ref_ptr.clone();
221                assert!(!destroyed.load(Ordering::Relaxed));
222
223                // Drop clone
224                drop(ref_ptr_clone);
225                assert!(!destroyed.load(Ordering::Relaxed));
226            } // Drop ref_ptr -> count becomes 0 -> calls recycle -> calls C++ release!
227
228            assert!(destroyed.load(Ordering::Relaxed));
229        }
230    }
231
232    #[test]
233    fn test_opaque_ref_counted_allocate_fails() {
234        let val = OpaqueRefCounted(Opaque::uninit());
235        let res = OpaqueRefCounted::<TestCppRefCountedObject>::allocate(val);
236        assert!(res.is_err());
237    }
238
239    pub struct TestCppFacadeBase;
240    unsafe impl Recyclable for TestCppFacadeBase {
241        unsafe fn recycle(ptr: NonNull<Self>) {
242            unsafe {
243                destroy_cpp_ref_counted_object(ptr.as_ptr() as *mut c_void);
244            }
245        }
246    }
247    impl HasRefCount for TestCppFacadeBase {
248        fn ref_count(&self) -> &RefCounted {
249            unsafe { &*(self as *const Self as *const RefCounted) }
250        }
251    }
252
253    #[repr(C)]
254    pub struct TestSubtypeFacade {
255        _facade: OpaqueRefCountedFacade<TestCppFacadeBase>,
256    }
257    impl Deref for TestSubtypeFacade {
258        type Target = TestCppFacadeBase;
259        fn deref(&self) -> &Self::Target {
260            unsafe { &*(self as *const Self as *const TestCppFacadeBase) }
261        }
262    }
263    unsafe impl IsOpaqueRefCounted for TestSubtypeFacade {
264        type TargetBase = TestCppFacadeBase;
265    }
266
267    #[test]
268    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
269    fn test_facade_ref_ptr() {
270        use core::sync::atomic::{AtomicBool, Ordering};
271
272        let destroyed = AtomicBool::new(false);
273        unsafe {
274            let raw_ptr = create_cpp_ref_counted_object(destroyed.as_ptr());
275            assert!(!destroyed.load(Ordering::Relaxed));
276
277            {
278                let ref_ptr = RefPtr::from_raw(raw_ptr as *mut TestSubtypeFacade);
279                assert!(!destroyed.load(Ordering::Relaxed));
280
281                let ref_ptr_clone = ref_ptr.clone();
282                assert!(!destroyed.load(Ordering::Relaxed));
283
284                drop(ref_ptr_clone);
285                assert!(!destroyed.load(Ordering::Relaxed));
286            }
287
288            assert!(destroyed.load(Ordering::Relaxed));
289        }
290    }
291
292    unsafe extern "C" fn test_release_fn(ptr: *mut TestMacroFacade) {
293        unsafe {
294            destroy_cpp_ref_counted_object(ptr as *mut c_void);
295        }
296    }
297
298    impl_opaque_ref_counted_facade!(
299        pub struct TestMacroFacade,
300        test_release_fn,
301    );
302
303    #[test]
304    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
305    fn test_macro_facade_ref_ptr() {
306        use core::sync::atomic::{AtomicBool, Ordering};
307
308        let destroyed = AtomicBool::new(false);
309        unsafe {
310            let raw_ptr = create_cpp_ref_counted_object(destroyed.as_ptr());
311            assert!(!destroyed.load(Ordering::Relaxed));
312
313            {
314                let ref_ptr = RefPtr::from_raw(raw_ptr as *mut TestMacroFacade);
315                assert!(!destroyed.load(Ordering::Relaxed));
316
317                let ref_ptr_clone = ref_ptr.clone();
318                assert!(!destroyed.load(Ordering::Relaxed));
319
320                drop(ref_ptr_clone);
321                assert!(!destroyed.load(Ordering::Relaxed));
322            }
323
324            assert!(destroyed.load(Ordering::Relaxed));
325        }
326    }
327
328    unsafe extern "C" fn test_get_ref_counted_fn(
329        ptr: *mut TestMacroFacadeWithGetter,
330    ) -> *mut RefCounted {
331        ptr as *mut RefCounted
332    }
333
334    unsafe extern "C" fn test_release_fn_with_getter(ptr: *mut TestMacroFacadeWithGetter) {
335        unsafe {
336            destroy_cpp_ref_counted_object(ptr as *mut c_void);
337        }
338    }
339
340    impl_opaque_ref_counted_facade!(
341        pub struct TestMacroFacadeWithGetter,
342        test_release_fn_with_getter,
343        test_get_ref_counted_fn,
344    );
345
346    #[test]
347    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
348    fn test_macro_facade_with_getter_ref_ptr() {
349        use core::sync::atomic::{AtomicBool, Ordering};
350
351        let destroyed = AtomicBool::new(false);
352        unsafe {
353            let raw_ptr = create_cpp_ref_counted_object(destroyed.as_ptr());
354            assert!(!destroyed.load(Ordering::Relaxed));
355
356            {
357                let ref_ptr = RefPtr::from_raw(raw_ptr as *mut TestMacroFacadeWithGetter);
358                assert!(!destroyed.load(Ordering::Relaxed));
359
360                let ref_ptr_clone = ref_ptr.clone();
361                assert!(!destroyed.load(Ordering::Relaxed));
362
363                drop(ref_ptr_clone);
364                assert!(!destroyed.load(Ordering::Relaxed));
365            }
366
367            assert!(destroyed.load(Ordering::Relaxed));
368        }
369    }
370}