[xcode14.1] [ObjCRuntime] Change Runtime.GetNSObject<T> to create a new instance if the existing one is of the wrong type. Fixes #13704.#16502
Conversation
…if the existing one is of the wrong type. Fixes dotnet#13704. Objective-C has an optimization where creating an empty dictionary would return the same instance every time (probably to a constant empty dictionary). This causes a problem with how we've bound generic dictionaries, because all empty dictionaries would have the same native handle, even if we'd bound them with different managed types. This surfaces in unfortunate ways when we try to look up a managed instance given a native handle, we find that we already have a managed instance, but the managed instance is of the wrong type. Example code: var a = new NSDictionary (); var b = new NSDictionary<NSString, NSString> (); var c = new NSDictionary<NSString, NSObject> (); Console.WriteLine ($"a: 0x{a.Handle:X}"); Console.WriteLine ($"b: 0x{b.Handle:X}"); Console.WriteLine ($"c: 0x{c.Handle:X}"); would produce something like: a: 0x0x7fff80821080 b: 0x0x7fff80821080 c: 0x0x7fff80821080 now for this code: Runtime.GetNSObject<NSDictionary<NSString, NSString>> (b.Handle) it would throw an exception like this: Unable to cast object of type 'Foundation.NSDictionary`2[[Foundation.NSString],[Foundation.NSObject]]' to type 'Foundation.NSDictionary`2[[Foundation.NSString],[Foundation.NSString]]' because we'll have the 'c' object (with type `NSDictionary<NSString, Object>`) in our dictionary of native handles -> managed instances, and that's not compatible with the desired return type from GetNSObject (`NSDictionary<NSString, NSString>`) This likely happens with all the non-mutable collection types we have a generic version of (NSArray, NSDictionary, NSOrderedSet, NSSet). Fixes dotnet#16378. Fixes dotnet#13704.
✅ API diff for current PR / commitLegacy Xamarin (No breaking changes)
NET (empty diffs)
❗ API diff vs stable (Breaking changes)Legacy Xamarin (:heavy_exclamation_mark: Breaking changes :heavy_exclamation_mark:).NET (:heavy_exclamation_mark: Breaking changes :heavy_exclamation_mark:)✅ Generator diffGenerator diff is empty Pipeline on Agent |
❌ [PR Build] Tests on macOS M1 - Mac Big Sur (11.5) failed ❌Failed tests are:
Pipeline on Agent |
🚀 [CI Build] Test results 🚀Test results✅ All tests passed on VSTS: simulator tests. 🎉 All 223 tests passed 🎉 Tests counts✅ bcl: All 69 tests passed. Html Report (VSDrops) Download Pipeline on Agent |
Objective-C has an optimization where creating an empty dictionary would
return the same instance every time (probably to a constant empty dictionary).
This causes a problem with how we've bound generic dictionaries, because all
empty dictionaries would have the same native handle, even if we'd bound them
with different managed types.
This surfaces in unfortunate ways when we try to look up a managed instance
given a native handle, we find that we already have a managed instance, but
the managed instance is of the wrong type.
Example code:
would produce something like:
now for this code:
it would throw an exception like this:
because we'll have the 'c' object (with type
NSDictionary<NSString, Object>)in our dictionary of native handles -> managed instances, and that's not
compatible with the desired return type from GetNSObject
(
NSDictionary<NSString, NSString>)This likely happens with all the non-mutable collection types we have a
generic version of (NSArray, NSDictionary, NSOrderedSet, NSSet).
Fixes #16378.
Fixes #13704.
This PR is somewhat simpler to review by ignoring whitespace: https://github.com/xamarin/xamarin-macios/pull/16491/files?w=1
Backport of #16491