diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets
index dc7dff2aa87..fda2bdb0b96 100644
--- a/dotnet/targets/Xamarin.Shared.Sdk.targets
+++ b/dotnet/targets/Xamarin.Shared.Sdk.targets
@@ -573,6 +573,9 @@
<_ValidateObjectPointers Condition="'$(_ValidateObjectPointers)' == ''">false
+
+ <_DisposeTaggedPointers Condition="'$(_DisposeTaggedPointers)' == ''">true
+
<_CustomLinkerOptions>
AreAnyAssembliesTrimmed=$(_AreAnyAssembliesTrimmed)
AssemblyName=$(AssemblyName).dll
@@ -687,6 +690,7 @@
+
dispose_tagged_pointers;
+ set => dispose_tagged_pointers = value;
+ }
+
protected virtual void Dispose (bool disposing)
{
if (disposed)
return;
disposed = true;
- if (handle != NativeHandle.Zero) {
+ /* Tagged pointer is limited to 64bit, which is all we support anyway.
+ *
+ * The tagged pointer bit is:
+ *
+ * Arm64: most significant bit
+ * Simulators (both on arm64 and x64 desktops): most significant bit
+ * Desktop/x64 (macOS + Mac Catalyst): least significant bit
+ * Ref: https://github.com/apple-oss-distributions/objc4/blob/89543e2c0f67d38ca5211cea33f42c51500287d5/runtime/objc-internal.h#L603-L672
+ */
+#if __MACOS__ || __MACCATALYST__
+ ulong _OBJC_TAG_MASK;
+ if (Runtime.IsARM64CallingConvention) {
+ _OBJC_TAG_MASK = 1UL << 63;
+ } else {
+ _OBJC_TAG_MASK = 1UL;
+ }
+#else
+ const ulong _OBJC_TAG_MASK = 1UL << 63;
+#endif
+
+ bool isTaggedPointer;
+ unchecked {
+ var ulongHandle = (ulong) (IntPtr) handle;
+ isTaggedPointer = (ulongHandle & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
+ }
+
+ if (!DisposeTaggedPointers && isTaggedPointer) {
+ // don't dispose tagged pointers.
+ FreeData (); // still need to do this though.
+ } else if (handle != NativeHandle.Zero) {
if (disposing) {
ReleaseManagedRef ();
} else {
diff --git a/src/ILLink.Substitutions.macOS.xml b/src/ILLink.Substitutions.macOS.xml
index 67e0e9963ea..6f2f5257d54 100644
--- a/src/ILLink.Substitutions.macOS.xml
+++ b/src/ILLink.Substitutions.macOS.xml
@@ -1,5 +1,9 @@
+
+
+
+
diff --git a/tests/monotouch-test/ObjCRuntime/TaggedPointerTest.cs b/tests/monotouch-test/ObjCRuntime/TaggedPointerTest.cs
new file mode 100644
index 00000000000..988d5bf34cb
--- /dev/null
+++ b/tests/monotouch-test/ObjCRuntime/TaggedPointerTest.cs
@@ -0,0 +1,54 @@
+using System;
+
+using Foundation;
+
+using NUnit.Framework;
+
+namespace MonoTouchFixtures.ObjCRuntime {
+
+ [TestFixture]
+ [Preserve (AllMembers = true)]
+ public class TaggedPointerTest {
+
+ [Test]
+ public void TaggedPointersArentDisposed ()
+ {
+ var notificationData =
+ """
+ {
+ action = "action";
+ aps =
+ {
+ category = "ee";
+ "content-available" = dd;
+ "mutable-content" = cc;
+ sound = bb;
+ "thread-id" = "aa";
+ };
+ "em-account" = "a";
+ "em-account-id" = "b";
+ "em-body" = "c";
+ "em-date" = "d";
+ "em-from" = "e";
+ "em-from-address" = "f";
+ "em-notification" = g;
+ "em-notification-id" = h;
+ "em-subject" = "i";
+ "gcm.message_id" = j;
+ "google.c.a.e" = k;
+ "google.c.fid" = l;
+ "google.c.sender.id" = m;
+ }
+ """;
+
+ var data = NSData.FromString (notificationData);
+ var fmt = NSPropertyListFormat.OpenStep;
+ var userInfo = (NSMutableDictionary) NSPropertyListSerialization.PropertyListWithData (data, NSPropertyListReadOptions.Immutable, ref fmt, out var error);
+ var apsKey = new NSString ("aps");
+ // Iteration here should not throw ObjectDisposedExceptions
+ foreach (var kv in userInfo) {
+ apsKey.Dispose ();
+ }
+ }
+ }
+}