From 78591d3a204af92b1e5124be7e8cc0a591898fe7 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Thu, 23 Oct 2025 16:05:34 +0300 Subject: [PATCH] Fix leak by freeing NSObjectData Every NSObject allocates native memory for `NSObjectData` via a `NSObjectDataHandle` critical handle. When the handle dies, the memory wasn't reclaimed because `IsInvalid` method was implemented incorrectly. Fixing this bug, leads to crashes in `ReleaseManagedRef` which tries to access the native data that was already freed. Normally, this shouldn't happen because `NSObject` is a normal finalizable object and `NSObjectDataHandle` is a critical finalizable object. This means that the finalizer for `NSObject` would always run first, considering that the 2 objects die at the same time. This means that the native `NSObjectData` would be cleared only after the finalizer for `NSObject` has run and `ReleaseManagedRef` finished executing. However, this is not the case because the "finalization" code of `NSObject` is not run from the finalizer thread but it is enqueued to `NSObject_Disposer`. This means that now the critical finalizer could have actually released the native memory before the finalization of the `NSObject` is done. The simple fix for this is to create a GCHandle keeping the `NSObjectDataHandle` alive until all `NSObject` finalization is done, at the end of `ReleaseManagedRef`. --- src/Foundation/NSObject2.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Foundation/NSObject2.cs b/src/Foundation/NSObject2.cs index b93f509d7761..8d528554c3dc 100644 --- a/src/Foundation/NSObject2.cs +++ b/src/Foundation/NSObject2.cs @@ -90,7 +90,7 @@ public unsafe NSObjectData* Data { } public override bool IsInvalid { - get => handle != IntPtr.Zero; + get => handle == IntPtr.Zero; } protected override bool ReleaseHandle () @@ -147,6 +147,7 @@ unsafe NativeHandle handle { // having to attach those threads to to the managed runtime. #nullable enable NSObjectDataHandle? data_handle; + GCHandle data_handle_gchandle; internal unsafe NSObjectData* GetData () { @@ -169,6 +170,7 @@ unsafe NSObjectDataHandle AllocateData () if (!Runtime.IsCoreCLR) // This condition (and the assignment to __handle_for_mono if applicable) is trimmed away by the linker. __data_for_mono = data.Data; + data_handle_gchandle = GCHandle.Alloc (data); return data; } @@ -452,6 +454,7 @@ void ReleaseManagedRef () Runtime.NativeObjectHasDied (handle, this); } xamarin_release_managed_ref (handle, user_type.AsByte ()); + data_handle_gchandle.Free (); } static bool IsProtocol (Type type, IntPtr protocol)