Skip to content

[Foundation] Fix NSObjectData leak. - #24114

Merged
rolfbjarne merged 6 commits into
mainfrom
dev/rolf/leak-fix-nsobjectdata
Dec 5, 2025
Merged

[Foundation] Fix NSObjectData leak.#24114
rolfbjarne merged 6 commits into
mainfrom
dev/rolf/leak-fix-nsobjectdata

Conversation

@rolfbjarne

@rolfbjarne rolfbjarne commented Oct 27, 2025

Copy link
Copy Markdown
Member

Every NSObject allocates native memory for NSObjectData via a NSObjectDataHandle critical handle. When the handle dies, the memory wasn't reclaimed because the NSObjectDataHandle.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.

Fix this by creating a new NSObjectDataHandle, wrapping the same native memory pointer and preventing the previous NSObjectDataHandle from freeing the memory, when scheduling the actual finalization of the NSObject instance to the main thread.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a memory leak in NSObjectData by addressing the lifetime management of NSObjectDataHandle instances during object finalization and resurrection. The core issue stems from CriticalHandle being collected when the parent object becomes finalizable, even if the object is later resurrected - which happens for all NSObject instances in this codebase.

Key Changes

  • Fixed critical bug in NSObjectDataHandle.IsInvalid property logic (was inverted)
  • Added RecreateDataHandle() method to create a new handle pointing to the same native memory during finalization, preventing premature collection
  • Added Invalidate() method and invalidated field to prevent double-freeing of native memory

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/Foundation/NSObject2.cs Core fix: Added RecreateDataHandle mechanism, fixed IsInvalid logic, added handle invalidation support to prevent memory leaks during object resurrection
tests/dotnet/UnitTests/expected/iOS-MonoVM-preservedapis.txt Updated preserved API list to reflect new public members (RecreateDataHandle, Invalidate, new constructor, invalidated field)
tests/dotnet/UnitTests/expected/iOS-MonoVM-interpreter-preservedapis.txt Updated preserved API list for interpreter configuration with same new members

Comment thread src/Foundation/NSObject2.cs
Comment thread src/Foundation/NSObject2.cs
Comment thread src/Foundation/NSObject2.cs Outdated
// nothing to do here.
} else {
unsafe {
NativeMemory.Free ((void*) handle);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this seems impossible in practice, I'm wondering whether there is a theoretical bug of accessing this memory after it was freed, particularly as we move over to CoreCLR which collects objects more aggressively compared to mono, where we used to pin a lot. We have the constraint of NSObject keeping NSObjectData alive. Consider the code:

set { GetData ()->flags = value; }

which is translates to

NSObjectData* data = this->GetData();
// `this` could potentially be collected at this point, with the data going down with it
data->flags = value;

A fix for this would be to add GC.KeepAlive(this) after access data.

Not really related to this particular change, but just something that I noticed which might be problematic in theory, as we will start running on CoreCLR. There might be other bits around hitting patterns like this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already use (and run tests on) CoreCLR on macOS, so it's been at least validated that if there are any race conditions, they're not very frequent (since the tests are passing).

That said:

NSObjectData* data = this->GetData();
// `this` could potentially be collected at this point, with the data going down with it
data->flags = value;

I guess this can happen if the GC runs to completion at line 2, collecting all normal objects and then also all CriticalHandles / critical finalizers, before line 3 is executed, so I'll add the GC.KeepAlive.

Going forward, for CoreCLR, it will probably make sense to use ObjectiveCMarshal.CreateReferenceTrackingHandle's tagged memory as a replacement for NSObjectData; the downside would be that we'd have very different code paths between Mono and CoreCLR.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@rolfbjarne
rolfbjarne enabled auto-merge (squash) December 5, 2025 11:06
@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ [PR Build #aadfba4] Build passed (Detect API changes) ✅

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ [CI Build #aadfba4] Build passed (Build packages) ✅

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ API diff for current PR / commit

NET (empty diffs)

✅ API diff vs stable

NET (empty diffs)

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ [CI Build #aadfba4] Build passed (Build macOS tests) ✅

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

💻 [CI Build #aadfba4] Tests on macOS X64 - Mac Sonoma (14) passed 💻

All tests on macOS X64 - Mac Sonoma (14) passed.

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

💻 [CI Build #aadfba4] Tests on macOS arm64 - Mac Tahoe (26) passed 💻

All tests on macOS arm64 - Mac Tahoe (26) passed.

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

💻 [CI Build #aadfba4] Tests on macOS M1 - Mac Monterey (12) passed 💻

All tests on macOS M1 - Mac Monterey (12) passed.

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

💻 [CI Build #aadfba4] Tests on macOS M1 - Mac Ventura (13) passed 💻

All tests on macOS M1 - Mac Ventura (13) passed.

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

💻 [CI Build #aadfba4] Tests on macOS arm64 - Mac Sequoia (15) passed 💻

All tests on macOS arm64 - Mac Sequoia (15) passed.

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🚀 [CI Build #aadfba4] Test results 🚀

Test results

✅ All tests passed on VSTS: test results.

🎉 All 117 tests passed 🎉

Tests counts

✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (iOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (MacCatalyst): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (macOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (tvOS): All 1 tests passed. Html Report (VSDrops) Download
✅ framework: All 2 tests passed. Html Report (VSDrops) Download
✅ fsharp: All 4 tests passed. Html Report (VSDrops) Download
✅ generator: All 5 tests passed. Html Report (VSDrops) Download
✅ interdependent-binding-projects: All 4 tests passed. Html Report (VSDrops) Download
✅ introspection: All 4 tests passed. Html Report (VSDrops) Download
✅ linker: All 44 tests passed. Html Report (VSDrops) Download
✅ monotouch (iOS): All 9 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 11 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 9 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 9 tests passed. Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ windows: All 3 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 4 tests passed. Html Report (VSDrops) Download
✅ xtro: All 1 tests passed. Html Report (VSDrops) Download

Pipeline on Agent
Hash: aadfba4f5d3dddc5d03d68d048438cd65090fb96 [PR build]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants