Fix leak in generated iOS FFI wrappers by holding Retained<AnyObject>

This commit is contained in:
nicoburns authored and Stephen Waits committed 2026-09-05 20:29:37 -06:00
1 parent 57d6794ad6
commit 6adf7ba465
1 file changed
+18 -9
+18 -9
View File
@@ -724,10 +724,10 @@ impl FfiBridgeParser {
let class_name_bytes = format!("{}\0", name); let class_name_bytes = format!("{}\0", name);
let type_def = quote! { let type_def = quote! {
/// Opaque wrapper around an Objective-C object pointer /// Opaque wrapper around a retained Objective-C object
/// The actual Swift class is looked up dynamically at runtime after dx links everything /// The actual Swift class is looked up dynamically at runtime after dx links everything
pub struct #name { pub struct #name {
inner: *mut manganis::objc2::runtime::AnyObject, inner: manganis::objc2::rc::Retained<manganis::objc2::runtime::AnyObject>,
} }
unsafe impl Send for #name {} unsafe impl Send for #name {}
@@ -827,17 +827,26 @@ impl FfiBridgeParser {
let instance: *mut manganis::objc2::runtime::AnyObject = manganis::objc2::msg_send![class, alloc]; let instance: *mut manganis::objc2::runtime::AnyObject = manganis::objc2::msg_send![class, alloc];
let instance: *mut manganis::objc2::runtime::AnyObject = manganis::objc2::msg_send![instance, init]; let instance: *mut manganis::objc2::runtime::AnyObject = manganis::objc2::msg_send![instance, init];
if instance.is_null() { // `alloc` + `init` returns a +1 retained object; `Retained::from_raw`
return Err("Failed to initialize instance"); // takes over that reference and sends `release` on drop.
} let inner = manganis::objc2::rc::Retained::from_raw(instance)
.ok_or("Failed to initialize instance")?;
Ok(Self { inner: instance }) Ok(Self { inner })
} }
} }
/// Create from an existing object pointer /// Create from an existing object pointer.
///
/// # Safety
///
/// `ptr` must be a valid, non-null pointer to an Objective-C object,
/// and the caller must transfer ownership of one retain count (+1)
/// to the returned wrapper, which sends `release` on drop.
pub unsafe fn from_raw(ptr: *mut manganis::objc2::runtime::AnyObject) -> Self { pub unsafe fn from_raw(ptr: *mut manganis::objc2::runtime::AnyObject) -> Self {
Self { inner: ptr } let inner = manganis::objc2::rc::Retained::from_raw(ptr)
.expect("from_raw called with a null pointer");
Self { inner }
} }
} }
}; };
@@ -960,7 +969,7 @@ impl FfiBridgeParser {
// Build the msg_send call // Build the msg_send call
let this_expr = if func.receiver.is_some() { let this_expr = if func.receiver.is_some() {
quote! { this.inner } quote! { manganis::objc2::rc::Retained::as_ptr(&this.inner) as *mut manganis::objc2::runtime::AnyObject }
} else { } else {
// For static methods, we'd need the class // For static methods, we'd need the class
quote! { class } quote! { class }