Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disposing immutable objects can lead to unpredictable behavior when Objective-C runtime does object pooling #21425

Open
filipnavara opened this issue Oct 12, 2024 · 0 comments

Comments

@filipnavara
Copy link
Contributor

filipnavara commented Oct 12, 2024

Consider the following code:

string notificationData =
	"""
	{
		action = "show_pushed_mail";
		aps =
		{
			alert =
			{
				body = "Compared Exchange Rates are out of tolerance in number of 115.";
				subtitle = "[email protected]";
				title = "eM Client Licensing";
			};
			category = "PUSHED_MAIL";
			"content-available" = 1;
			"mutable-content" = 1;
			sound = default;
			"thread-id" = "mail_cf73f3bd-1cf9-437c-bc70-4b6bd2b5f7e4";
		};
		"em-account" = "[email protected]";
		"em-account-id" = "cf73f3bd-1cf9-437c-bc70-4b6bd2b5f7e4";
		"em-body" = "";
		"em-date" = "2024-10-09T01:30:01.0000000Z";
		"em-from" = "eM Client Licensing";
		"em-from-address" = "[email protected]";
		"em-message-path" = "{\n \"Mailbox\": \"INBOX\",\n \"UIDVALIDITY\": \"1117940911\",\n \"UID\": \"317970\",\n \"Message-ID\": \"[email protected]\"\n}";
		"em-notification" = Mixed;
		"em-notification-id" = 5801afc0ceb977f379adc152333e0d25554a98d4;
		"em-subject" = "Compared Exchange Rates are out of tolerance in number of 115.";
		"gcm.message_id" = 1728437407395630;
		"google.c.a.e" = 1;
		"google.c.fid" = eEhP3hWX1U8DuSB9hb3siN;
		"google.c.sender.id" = 417058856903;
	}
	""";

var d = NSData.FromString(notificationData);
NSPropertyListFormat fmt = NSPropertyListFormat.OpenStep;
var userInfo = (NSMutableDictionary)NSPropertyListSerialization.PropertyListWithData(d, ref fmt, out var error);

var apsKey = new NSString("aps");
var data = userInfo
	.Where(kv =>
	{
		var r = !kv.Key.ToString().Equals("aps", StringComparison.OrdinalIgnoreCase);
		apsKey.Dispose();
		return r;
	})
	.ToDictionary(kv => kv.Key.ToString(), kv => kv.Value.ToString() ?? string.Empty);

It will reliably crash with ObjectDisposedException which may be quite unexpected. In fact, this is a very reduced example of a problem that was happening in a multi-threaded application where it was even less obvious.

Why does it crash?

  • We create the NSString object representing the string aps.
  • When Objective-C enumerates the dictionary keys it reuses the same object, ie. same handle, when enumerating the aps key, and that in turns maps to the same managed NSString object.
  • Calling Dispose on the NSString causes the managed representation to be invalid and next enumeration of the same key will crash with ObjectDisposedException.

What can we do about it?

Changing the behavior to remove disposed objects from the handle->managed object mapping seems dangerous. (would not help anyway in the original multi-threaded scenario)

Make the Dispose on immutable poolable classes like NSString a no-op? Make an analyzer that warns when someone tries to dispose a NSString instance (would flag the obvious error but not if someone disposes it as NSObject variable)?

Thoughts?

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

No branches or pull requests

1 participant