gpui: Fix macOS memory leaks (#45051)

Anthony Eid created

The below memory leaks were caused by failing to release reference
counted resources. I confirmed using instruments that my changes stopped
the leaks from occurring.

- System prompts 
- Screen capturing 
- loading font families

There were also two memory leaks I found from some of our dependencies
that I made PRs to fix
- https://github.com/RustAudio/coreaudio-rs/pull/147
- https://github.com/servo/core-foundation-rs/pull/746

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/mac/open_type.rs      |  5 +++
crates/gpui/src/platform/mac/screen_capture.rs | 14 +++++++++-
crates/gpui/src/platform/mac/text_system.rs    | 26 ++++++++++++++++++-
crates/gpui/src/platform/mac/window.rs         |  1 
4 files changed, 42 insertions(+), 4 deletions(-)

Detailed changes

crates/gpui/src/platform/mac/open_type.rs 🔗

@@ -52,6 +52,11 @@ pub fn apply_features_and_fallbacks(
             &kCFTypeDictionaryKeyCallBacks,
             &kCFTypeDictionaryValueCallBacks,
         );
+
+        for value in &values {
+            CFRelease(*value as _);
+        }
+
         let new_descriptor = CTFontDescriptorCreateWithAttributes(attrs);
         CFRelease(attrs as _);
         let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);

crates/gpui/src/platform/mac/screen_capture.rs 🔗

@@ -110,13 +110,21 @@ impl ScreenCaptureSource for MacScreenCaptureSource {
             let _: id = msg_send![configuration, setHeight: meta.resolution.height.0 as i64];
             let stream: id = msg_send![stream, initWithFilter:filter configuration:configuration delegate:delegate];
 
+            // Stream contains filter, configuration, and delegate internally so we release them here
+            // to prevent a memory leak when steam is dropped
+            let _: () = msg_send![filter, release];
+            let _: () = msg_send![configuration, release];
+            let _: () = msg_send![delegate, release];
+
             let (mut tx, rx) = oneshot::channel();
 
             let mut error: id = nil;
             let _: () = msg_send![stream, addStreamOutput:output type:SCStreamOutputTypeScreen sampleHandlerQueue:0 error:&mut error as *mut id];
             if error != nil {
                 let message: id = msg_send![error, localizedDescription];
-                tx.send(Err(anyhow!("failed to add stream  output {message:?}")))
+                let _: () = msg_send![stream, release];
+                let _: () = msg_send![output, release];
+                tx.send(Err(anyhow!("failed to add stream output {message:?}")))
                     .ok();
                 return rx;
             }
@@ -132,8 +140,10 @@ impl ScreenCaptureSource for MacScreenCaptureSource {
                         };
                         Ok(Box::new(stream) as Box<dyn ScreenCaptureStream>)
                     } else {
+                        let _: () = msg_send![stream, release];
+                        let _: () = msg_send![output, release];
                         let message: id = msg_send![error, localizedDescription];
-                        Err(anyhow!("failed to stop screen capture stream {message:?}"))
+                        Err(anyhow!("failed to start screen capture stream {message:?}"))
                     };
                     if let Some(tx) = tx.borrow_mut().take() {
                         tx.send(result).ok();

crates/gpui/src/platform/mac/text_system.rs 🔗

@@ -8,6 +8,7 @@ use anyhow::anyhow;
 use cocoa::appkit::CGFloat;
 use collections::HashMap;
 use core_foundation::{
+    array::{CFArray, CFArrayRef},
     attributed_string::CFMutableAttributedString,
     base::{CFRange, TCFType},
     number::CFNumber,
@@ -21,8 +22,10 @@ use core_graphics::{
 };
 use core_text::{
     font::CTFont,
+    font_collection::CTFontCollectionRef,
     font_descriptor::{
-        kCTFontSlantTrait, kCTFontSymbolicTrait, kCTFontWeightTrait, kCTFontWidthTrait,
+        CTFontDescriptor, kCTFontSlantTrait, kCTFontSymbolicTrait, kCTFontWeightTrait,
+        kCTFontWidthTrait,
     },
     line::CTLine,
     string_attributes::kCTFontAttributeName,
@@ -97,7 +100,26 @@ impl PlatformTextSystem for MacTextSystem {
     fn all_font_names(&self) -> Vec<String> {
         let mut names = Vec::new();
         let collection = core_text::font_collection::create_for_all_families();
-        let Some(descriptors) = collection.get_descriptors() else {
+        // NOTE: We intentionally avoid using `collection.get_descriptors()` here because
+        // it has a memory leak bug in core-text v21.0.0. The upstream code uses
+        // `wrap_under_get_rule` but `CTFontCollectionCreateMatchingFontDescriptors`
+        // follows the Create Rule (caller owns the result), so it should use
+        // `wrap_under_create_rule`. We call the function directly with correct memory management.
+        unsafe extern "C" {
+            fn CTFontCollectionCreateMatchingFontDescriptors(
+                collection: CTFontCollectionRef,
+            ) -> CFArrayRef;
+        }
+        let descriptors: Option<CFArray<CTFontDescriptor>> = unsafe {
+            let array_ref =
+                CTFontCollectionCreateMatchingFontDescriptors(collection.as_concrete_TypeRef());
+            if array_ref.is_null() {
+                None
+            } else {
+                Some(CFArray::wrap_under_create_rule(array_ref))
+            }
+        };
+        let Some(descriptors) = descriptors else {
             return names;
         };
         for descriptor in descriptors.into_iter() {

crates/gpui/src/platform/mac/window.rs 🔗

@@ -1190,6 +1190,7 @@ impl PlatformWindow for MacWindow {
             let (done_tx, done_rx) = oneshot::channel();
             let done_tx = Cell::new(Some(done_tx));
             let block = ConcreteBlock::new(move |answer: NSInteger| {
+                let _: () = msg_send![alert, release];
                 if let Some(done_tx) = done_tx.take() {
                     let _ = done_tx.send(answer.try_into().unwrap());
                 }