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::ref_counted::{HasRefCount, RefCounted};
8use zr::Opaque;
9
10/// A wrapper for C++ objects that are known to use `fbl::RefCounted`
11/// and have their reference count at offset 0.
12#[repr(transparent)]
13pub struct OpaqueRefCounted<T>(Opaque<T>);
14
15impl<T> OpaqueRefCounted<T> {
16    /// Returns a raw pointer to the opaque data.
17    pub fn get(&self) -> *mut T {
18        self.0.get()
19    }
20}
21
22impl<T> HasRefCount for OpaqueRefCounted<T> {
23    fn ref_count(&self) -> &RefCounted {
24        // SAFETY: OpaqueRefCounted guarantees that the ref count is at offset 0.
25        unsafe { &*(self.get() as *const RefCounted) }
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use crate::recyclable::Recyclable;
33    use crate::ref_ptr::RefPtr;
34    use core::ffi::c_void;
35    use core::ptr::NonNull;
36
37    unsafe extern "C" {
38        fn create_cpp_ref_counted_object(destroyed: *mut bool) -> *mut c_void;
39        fn destroy_cpp_ref_counted_object(ptr: *mut c_void);
40    }
41
42    pub struct TestCppRefCountedObject;
43
44    unsafe impl Recyclable for OpaqueRefCounted<TestCppRefCountedObject> {
45        unsafe fn recycle(ptr: NonNull<Self>) {
46            unsafe {
47                destroy_cpp_ref_counted_object(ptr.as_ptr() as *mut c_void);
48            }
49        }
50
51        fn allocate(_value: Self) -> Result<NonNull<Self>, ::kalloc::AllocError> {
52            Err(::kalloc::AllocError)
53        }
54    }
55
56    #[test]
57    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
58    fn test_cross_lang_ref_ptr() {
59        use core::sync::atomic::{AtomicBool, Ordering};
60
61        let destroyed = AtomicBool::new(false);
62        unsafe {
63            let raw_ptr = create_cpp_ref_counted_object(destroyed.as_ptr());
64            assert!(!destroyed.load(Ordering::Relaxed));
65
66            {
67                let ref_ptr =
68                    RefPtr::from_raw(raw_ptr as *mut OpaqueRefCounted<TestCppRefCountedObject>);
69                assert!(!destroyed.load(Ordering::Relaxed));
70
71                let ref_ptr_clone = ref_ptr.clone();
72                assert!(!destroyed.load(Ordering::Relaxed));
73
74                // Drop clone
75                drop(ref_ptr_clone);
76                assert!(!destroyed.load(Ordering::Relaxed));
77            } // Drop ref_ptr -> count becomes 0 -> calls recycle -> calls C++ release!
78
79            assert!(destroyed.load(Ordering::Relaxed));
80        }
81    }
82
83    #[test]
84    fn test_opaque_ref_counted_allocate_fails() {
85        let val = OpaqueRefCounted(Opaque::uninit());
86        let res = OpaqueRefCounted::<TestCppRefCountedObject>::allocate(val);
87        assert!(res.is_err());
88    }
89}