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