Open snechaev opened 4 years ago
This can be reproduced with a simpler sample:
var array1 = new NSArray ();
var array2 = NSArray.FromNSObjects (new NSObject [0]);
Console.WriteLine ("Array 1: {0}", array1.Handle);
Console.WriteLine ("Array 2: {0}", array2.Handle);
Console.WriteLine ("Array1 == Array2: {0}", object.ReferenceEquals (array1, array2));
array2.Dispose ();
Console.WriteLine ("Array 1: {0}", array1.Handle);
Console.WriteLine ("Array 2: {0}", array2.Handle);
this prints:
Array 1: 140735586813024
Array 2: 140735586813024
Array1 == Array2: True
Array 1: 0
Array 2: 0
this is because Objective-C has a singleton for an empty NSArray, and re-uses that singleton.
What happens here:
Dispose
on array2
(which is also the exact same managed instance as array1
), effectively disposing array1
as well.@snechaev is this behavior causing a problem for you? From what I can tell it shouldn't be harmful in any way, although I can certainly see that it looks very weird.
Hmm, I thinking before about the some cache/singleton for empty NSArray (the thought were based on the same Handles for different NSArray objects), but my experiments with swift do not confirmed this. It's sad, but looks like I made mistake somewhere in swift.
So, since it's cocoa-level singleton trick, then the behaviour is totally clear now.
As per your question: this behaviour inducted the crash in my app in the following use-after-free scenario.
I have following classes (simplified example).
The LargeObject
contains some data, for which I want to call Dispose
manually and do not wait the GC. The set of data for LargeObjects
is provided from the external source and may be empty.
public class LargeObject: NSObject, IDisposable
{
...
}
public class Container : NSObject, IDisposable
{
[Export("objects")]
NSArray<LargeObject> Objects { get; set; }
public Container(LargeObject[] objects)
{
Objects = NSArray<LargeObject>.FromNSObjects(objects);
}
protected override void Dispose(bool disposing)
{
if(disposing)
{
foreach (var obj in Objects)
{
obj.Dispose();
}
}
base.Dispose(disposing);
}
}
So, the crash is in following scenario:
Container
object with empty dataset - it leads to create an empty NSArray
ArrayController
as described in first post.Dispose
on Container
object. And it throws NullReferenceException
when trying to enumerate Objects
array (of course the Objects
itself is not null at the moment, but it's handle=0).Of course all works fine if the dataset is not empty or if user do not open the window with mentioned NSArrayController
.
In my situation I just switched to NSMutableArray.
Thanks a lot for the explanation, I now see how this ended up causing problems for you.
I understand that using an NSMutableArray is a viable workaround for you, and that you don't need this fixed, is that correct? I can't see an easy fix for this bug, which means it'll probably be some time until we can get around to fixing it.
Yes, the current workaround with NSMutableArray is good, so the fix for this issue is not vitally-important for me.
Steps to Reproduce
Minimal test code
Expected Behavior
Operations with NSArrayController do not affect any objects, which is not related to this array controller.
Actual Behavior
Calling NSArrayController.Remove leads to unrelated NSArray being destroyed (NSArray.Handle=0).
Additional details
There is NSArray local variable in the button handler and NSArrayController. The NSArray variable is initialised with an empty NSArray. The Array controller do not have any bindings and other connections, so it is completely unrelated to the NSArray variable mentioned above. The button handler calls Remove on the ArrayController in form of
arrayControllerUnbinded.Remove(arrayControllerUnbinded.ArrangedObjects());
After this call the NSArray variable Handle become zero.Some additional notes
Environment
Build Logs
Build log
Building 44 (Debug) Build started 07.09.2020 17:22:03. __________________________________________________ Project "/Users/sergey/work/44/44/44.csproj" (Build target(s)): Target _BeforeCoreCompileImageAssets: Skipping target "_BeforeCoreCompileImageAssets" because all output files are up-to-date with respect to the input files. Target _CoreCompileImageAssets: Skipping target "_CoreCompileImageAssets" because all output files are up-to-date with respect to the input files. Target _CoreCompileInterfaceDefinitions: BundleResources Output: obj/Debug/ibtool/Main.storyboardc/MainMenu.nib obj/Debug/ibtool/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib obj/Debug/ibtool/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib obj/Debug/ibtool/Main.storyboardc/Info.plist OutputManifests Output: obj/Debug/ibtool-manifests/Main.storyboardc Target _CoreCompileColladaAssets: Skipping target "_CoreCompileColladaAssets" because it has no inputs. Target GenerateTargetFrameworkMonikerAttribute: Skipping target "GenerateTargetFrameworkMonikerAttribute" because all output files are up-to-date with respect to the input files. Target CoreCompile: /Library/Frameworks/Mono.framework/Versions/6.12.0/lib/mono/msbuild/Current/bin/Roslyn/csc.exe /noconfig /nowarn:1701,1702,2008 /nostdlib+ /errorreport:prompt /warn:4 /define:__UNIFIED__;__MACOS__;DEBUG /errorendlocation /preferreduilang:en-US /reference:/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/mono/Xamarin.Mac/mscorlib.dll /reference:/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/mono/Xamarin.Mac/System.Core.dll /reference:/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/mono/Xamarin.Mac/System.dll /reference:/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/mono/Xamarin.Mac/Facades/System.Drawing.Common.dll /reference:/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/mono/Xamarin.Mac/Xamarin.Mac.dll /debug+ /debug:full /optimize- /out:obj/Debug/44.exe /target:exe /utf8output /langversion:7.3 Main.cs AppDelegate.cs ViewController.cs ViewController.designer.cs "obj/Debug/Xamarin.Mac,Version=v2.0.AssemblyAttributes.cs" Using shared compilation with compiler from directory: /Library/Frameworks/Mono.framework/Versions/6.12.0/lib/mono/msbuild/Current/bin/Roslyn Target CopyFilesToOutputDirectory: Copying file from "/Users/sergey/work/44/44/obj/Debug/44.exe" to "/Users/sergey/work/44/44/bin/Debug/44.exe". 44 -> /Users/sergey/work/44/44/bin/Debug/44.exe Copying file from "/Users/sergey/work/44/44/obj/Debug/44.pdb" to "/Users/sergey/work/44/44/bin/Debug/44.pdb". Target _DetectSigningIdentity: Detected signing identity: Bundle Id: com.companyname.x44 App Id: com.companyname.x44 Target _CopyContentToBundle: Skipping target "_CopyContentToBundle" because all output files are up-to-date with respect to the input files. Target _CompileAppManifest: Skipping target "_CompileAppManifest" because all output files are up-to-date with respect to the input files. Target _CompileToNative: /Library/Frameworks/Xamarin.Mac.framework/Versions/Current/bin/mmp @/Users/sergey/work/44/44/obj/Debug/response-file.rsp Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:06.53 ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== Build successful.Example Project (If Possible)
44.zip