EXC_BAD_ACCESS when closing NSDocument window (ARC enabled)

I'm working on converting a document-based application from Garbage Collection (it ran fine under 10.6) to Automatic Reference Counting (trying to get it to compile and run for 10.12). I'm getting a consistent EXC_BAD_ACCESS when the last window is closed. Nothing is flagged by the static analyser.


I used Instruments to look for Zombies, and indeed there appears to be a `release` message sent to a deallocated object. Here is the trace:


# Event ∆ RefCt Timestamp Responsible Library/Responsible Caller

173 Release -1 3 00:05.226.677 Foundation __48-[NSFileAccessArbiterProxy removeFilePresenter:]_block_invoke

174 Release -1 2 00:05.226.679 Foundation -[NSFilePresenterXPCMessenger invalidate]

175 Retain +1 3 00:05.226.823 Foundation -[NSBlockOperation initWithBlock:]

176 Retain +1 4 00:05.226.858 AppKit -[NSDocument close]

177 Release -1 3 00:05.227.350 Foundation -[NSFilePresenterXPCMessenger dealloc]

Retain/Release (2) 00:05.227.484 AppKit -[NSDocumentController removeDocument:]

180 Release -1 2 00:05.227.485 AppKit -[NSDocumentController removeDocument:]

Retain/Release (2) 00:05.227.496 AppKit -[NSUIActivityManager addProvider:toUserActivity:withSetter:]

183 Autorelease 00:05.227.499 AppKit -[NSWindowController _windowDidClose]

184 Release -1 1 00:05.228.172 Foundation -[NSAutoreleasePool drain]

185 Release -1 0 00:05.228.184 Foundation -[NSBlockOperation dealloc]

186 Zombie -1 00:05.242.579 AppKit -[NSApplication(NSWindowCache) _checkForTerminateAfterLastWindowClosed:saveWindows:]


The difficult I am having in diagnosing this problem is that I can see the message being sent to the Zombie, but because I do not release any objects (it is all done by ARC), I'm guessing there is an implicit release going on somewhere. But I'm not sure where to look.


When in the debugger, `lldb` complains of a crash in `main.m` at:


return NSApplicationMain(argc, (const char **) argv);


Any help would be greatly appreciated. Thank you.

Answered by QuinceyMorris in 230940022

>> the NSDocument subclass gets deallocated when the last document closes […] Does that sound right?


I wouldn't have thought so, but maybe. Anyway, it sounds like a terrible idea to use it. How do you implement delegate methods? As class methods?


>> would it be appropriate to set the MainMenu.xib controller as my App delegate instead of my NSDocument subclass?


I'm not sure what object you mean by controller. The way it's usually done (usually == almost always) is to put the App delegate object into MainMenu.xib. It's just a custom subclass of NSObject that adopts NSApplicationDelegate protocol. (If you need to create it, drag in an "Object" item from the IB library, the blue cube, and change its class to match the app delegate class you define in code.) Then this custom object is connected, in the XIB, as the delegate of the NSApplication pseudo-object that's also in the XIB. That means your app delegate is created when the main menu NIB is instantiated, and never goes away. You don't need any code to set this up.


In fact, this structure is what you get from an Xcode template, you hardly ever have to set it up yourself. Maybe your app was originally created from a very old template, or someone went to the trouble of throwing away the in-XIB app delegate, or some other weird scenario.

The chances are, it's a memory management bug (which you presumably know or suspect already). When converting from GC, there are two common possible errors: a retain cycle, and an unowned reference. Retain cycles don't crash, so this is likely an unowned reference. (An unowned reference is one that isn't strong, so it doesn't keep the referenced object alive, but isn't zeroing weak, so the reference becomes invalid as soon as the object is deallocated.)


Luckily for you, this scenario — an invalid reference causing a crash when closing a window — is kind of notorious. For historical reasons, there's a network of criss-crossing relationships between the various objects (document, window controller, window, as well as views and more), and it's easy to get dangling unowned references if things happen in an unlucky order. The usual culprit is a non-strong delegate property, or a non-strong parent reference from one of these objects to another, and the usual solution is to set the offending properties to nil.


For example, the NSWindow delegate property is "assign" in Obj-C terms, which means "unsafe unowned" in more recent terminology. If, during document closing, the window controller deallocates before the window, you may be headed for a crash. You can have the window controller set window.delegate to nil in windowWillClose, or (if that's too soon) in the window controller's dealloc.


If it's not that reference specifically, I'd bet it's something similar.

Thank you for your response. Yes, I am at this point assuming it is a memory management issue. Is there way to track down the culprit? It doesn't seem like using Zombies in Instruments is helping: it points to a problem, but does not help me target the problem. That is, the offending line according to Instruments appears to be "-[NSApplication(NSWindowCache) _checkForTerminateAfterLastWindowClosed:saveWindows:]"; but I never issued that line. I have made what might be a little progress towards figuring things out.


The structure I am using in the document is:


NSDocumentController (default, not subclassed)

NSDocument (subclassed, MyDocument; also the delegate of window)

NSWindowController (default, not subclassed)

NSWindow (MyDocument.xib, MainMenu.xib)


I tried setting the delegate of the open window to nil as follows:


-(void)windowWillClose:(NSNotification *)notification

{

windowOpen = YES;

NSArray *windowControllers = [self windowControllers];

NSWindowController *currentWindowController = [windowControllers firstObject];

NSWindow *windowForCurrentController = [currentWindowController window];

NSDocument *w = [windowForCurrentController delegate];

[windowForCurrentController setDelegate:nil];

}


This causes the same crash.


Then, I thought perhaps the currentWindowController (or the application) was messaging a deallocated window. So I tried adding the line:


[currentWindowController setWindow:nil];


This gets rid of the crashes, but introduces new problems (such as when trying to load new files, etc.). But I'm wondering if this is a clue to help solve the overall problem.

A couple of points:


— I know it's possible to have your NSDocument subclass be your main MVC controller (in particular, I would assume, it's your document XIB's "File's Owner"), but I highly recommend against it, except in the simplest of apps. It's better to subclass NSWindowController, too, and make that the window's delegate. In this case, using the document as the window delegate just involves more objects in the network of references that might jump up and bite you.


— If Zombies are detecting a problem, doesn't it also tell you the class of the deallocated object being referenced? That seems like an important piece of information.


— The window also has an unsafe unowned "windowController" property. It's not obvious, in your multi-object scenario, when the last reference from the window to the windowController occurs.


— Looking at some older projects, I see that the delegates I've explicitly set to nil are the data source and delegate of table/outline views, and the delegate of some other UI elements in the window. You should probably start examining the contents of your window, to see if you have delegates there that ought to be nil'ed in windowWillClose. Also, be careful of bindings through to your data model, since that might disappear when your document instance deallocates.


— Keep in mind that the window is likely the very last thing to be deallocated. That's because the Window Manager (a separate process) has a reference to it that keeps it alive. After all the other deallocations are done, your app returns to its main event loop, and only then does the Window Manager get informed that the window is being closed.

On point one: From what you are saying, it sounds ilke I will have to break up my NSDocument subclass. This seems reasonable; though I'd rather not if I don't have to, since it is deeply intwined with other the applciation as it is currently.


On point two: I have included all the information given to me by the Zombies in the original question. It seems to me that it is ... NSApplication (?). I cannot determine the sender. Any suggestions as to how to get more information out of the Zombie?


On point three: Taking this point under advisement, I issued the following line in -(void)windowWillClose:(NSNotification *)notification:


[windowForCurrentController setWindowController:nil];


As a result, the exit-crash seems to have gone away. From this, I am surmising that 1) the NSDocument subclass that is the delegate and window controller of the NSWindow instance is deallocated; then 2) a message is sent to the window controller. Does this sound reasonable as a diagnosis?


One tiny bit of strangeness when I do this: the "Save As..." and "Save" menu options remain even after the last window is closed (but the application itself is not closed); the "Close" option is ghosted out as expected. Do you think this points to my having to separate out the NSDocument subclass and window delegate? I was thinking I might just bind the availability of "Save As..." and "Save" to "Close"; that does sound a little kludgy though. I'm just wondering whether it would have any deleterious effects.


Thank you for your help in trying to track this down.

You shouldn't have to break up your NSDocument subclass. It's a good idea if you can, but not important enough to introduce chaos to achieve.


If setting the window's windowController to nil solves your problem, then I think that's fine. You may not have located the ultimate offender (it can be really hard to track down), but breaking the chain of references may be enough.


What happens if you choose "Save As"? In general, the menu item will stay enabled if there's an object in the responder chain that implements the corresponding action method. You could try setting a symbolic breakpoint on the action method (saveAs:, or whatever it is) and see who implements it. This may, for example, indicate that your document instance is in the responder chain and hasn't been deallocated, so you might be leaking memory — which would be worth knowing about.

This problem is still ongoing, and points to something deeper going on.



(I) This odd behaviour occurs when all documents are closed by the program is still running: When I select "Save As," a previously closed window, complete with the document, pops up with a Save Panel. When the Save Panel is closed, the document continues to exist!



I tried to get additional information on what is going on, I used Instruments/Allocations and then selected "Record Reference Counts." Instruments confirms that documents are never released: the number of persistent document objects increases by 1 each time I open/load, and never decreases.



Since I am upgrading this application from 10.6 which used Garbage Collection, it is not surprising that some issues of this sort have cropped up. I guess the question is: why are the documents not being released properly? I can't issue a release message under ARC; but I take it that the reason it is not being released is that there is a strong reference to the document object. But there's another problem:



(II) When I put a breakpoint in the -dealloc method of the document object, I find that -dealloc is not being called! Okay, so then, I commented out [windowForCurrentController setWindowController:nil]; in the -windowWillClose; and indeed, -dealloc is called. When I move [windowForCurrentController setWindowController:nil]; to -dealloc, the program crashes with EXC_BAD_ACCESS.



So, it seems more and more like I'm going to have to break up my NSDocument subclass to even have a chance at getting this to work; this seems like a morass. I'm just not sure what's going on here, and I don't know how to figure it out. As always, any suggestions would be greatly appreciated.

At this point, I'd suggest taking out the "windowWillClose" hack. From what you said, it's causing the retain cycle that leaks the document objects.


Let's go back to the original problem, where you got a EXC_BAD_ACCESS crash after the last window was closed. What's the backtrace (at least the first few entries) at the time of the crash? Also, do you have an Obj-C Exception breakpoint set in Xcode? It's important to have that so that you app will stop at any thrown exceptions.

Okay, I have removed the -windowWillClose hack, and I'm back to crashing on closing the final document window. I'll paste the backtrace at the bottom of this reply. When I set a break on exception throw, nothing happens: that is, the debugger breaks on exactly the same line when the break is not set. In terms of the backtrace, this coheres with the Zomie instrument: that is, -_checkForTerminateAfterLastWindowClosed seems to be the culperate. When I use Instruments to track Zombies (see output in original parent), it is apparently a deallocated instance of MyDocument that is sent a message.


Thanks again for helping me work through this.

———

Backtrace:


* thread #1: tid = 0xa07dda, 0x00007fffc202301d libobjc.A.dylib`objc_msgSend + 29, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x40dedeadbec0)

frame #0: 0x00007fffc202301d libobjc.A.dylib`objc_msgSend + 29

frame #1: 0x00007fffaaa87f50 AppKit`-[NSApplication(NSWindowCache) _checkForTerminateAfterLastWindowClosed:saveWindows:] + 85

frame #2: 0x00007fffaaa87ed0 AppKit`__79-[NSApplication(NSWindowCache) _scheduleCheckForTerminateAfterLastWindowClosed]_block_invoke + 102

frame #3: 0x00007fffacc04de4 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20

frame #4: 0x00007fffacc04a73 CoreFoundation`__CFRunLoopDoTimer + 1075

frame #5: 0x00007fffacc045ca CoreFoundation`__CFRunLoopDoTimers + 298

frame #6: 0x00007fffacbfbfa1 CoreFoundation`__CFRunLoopRun + 2081

frame #7: 0x00007fffacbfb524 CoreFoundation`CFRunLoopRunSpecific + 420

frame #8: 0x00007fffac15bebc HIToolbox`RunCurrentEventLoopInMode + 240

frame #9: 0x00007fffac15bcf1 HIToolbox`ReceiveNextEventCommon + 432

frame #10: 0x00007fffac15bb26 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 71

frame #11: 0x00007fffaa6f6e24 AppKit`_DPSNextEvent + 1120

frame #12: 0x00007fffaae7285e AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 2796

frame #13: 0x00007fffaa6eb7ab AppKit`-[NSApplication run] + 926

frame #14: 0x00007fffaa6b61de AppKit`NSApplicationMain + 1237

* frame #15: 0x000000010002bad2 Deductions`main(argc=3, argv=0x00007fff5fbff6c8) + 34 at main.m:13

frame #16: 0x00007fffc2912235 libdyld.dylib`start + 1

frame #17: 0x00007fffc2912235 libdyld.dylib`start + 1

It looks very much like your document instance has been overreleased. From that point of view, there are a couple of interesting things about your allocation trace in the original post.


It looks like the document has been closed (event #176) inside a block operation (#175 through #185). It wouldn't necessarily be odd for the document machinery to do stuff on an operation queue, but I can't imagine how it would want to call the public "close" method there. Is it possible that your code attempts to do the document close as an asynchronous operation? If so, on the main queue or another queue?


I'm wondering if the document is being closed on a background thread (which would be bad news for bindings or KVO notifications that affected UI elements), or in a thread-unsafe manner. Indeed, I'm wondering if that allocation history is showing events interleaved from two different threads.


The other odd thing is the autorelease pool drain (#184). The document is autoreleased in _windowDidClose (seems reasonable, since the window controller's "document" property is likely being nil'ed there), but the autorelease is turned into an actual release by what looks like a pool drain being done by the block operation. That's not entirely plausible.

I didn't read every reply in this thread since the posts are kind of long. But...


Does anything in your NSDocument get set as the delegate of a property that is still declared assign (not weak). If so, nil out all these delegates in dealloc one by one, see if you can track it down.


There are still a bunch of delegate properties in AppKit that haven't been converted to ARC.


Sounds like an object is outliving its delegate... and is attempting to send its delegate a message after it has already be deallocated. If the delegate property is still declared assign, you should have its delegate nil out the property in dealloc.

Regarding the document being closed (event #176) inside a block operation (#175 through #185): I set a symbolic breakpoint of -[NSDocument close], and the break seems to occur only once per document, and where one might expect it to occur. I have pasted in a portion of what shows up for Thread 1 at the bottom of this message. I note that there is a _block_invoke () at #2; but I'm not exactly sure what's going on at this point. I have also tried a break on -[NSDocument alloc], and this happens exactly where one would expect, and right after -[MyDocument alloc] (MyDocument is the subclass of NSDocument).


Regarding whether the document is being closed on a background thread: I am not intentionally using multithreaded operations, but I suppose that doesn't mean it isn't. But I'm wondering if there is some other way to track this other than setting a symbolic breakpoint.


Regarding the autorelease pool (event #184): So what you're saying here is the pool drain is causing the problem? I'm not exactly sure what to do next here. Do you have any suggestions?


Thank you again.

---


Here is Thread #1, when the symbolic breakpoint of -[NSDocument close] is triggered:


#0 0x00007fffaaa4d538 in -[NSDocument close] ()

#1 0x00007fffaa9b71aa in -[NSWindowController _windowDidClose] ()

#2 0x00007fffaa98e2fc in __18-[NSWindow _close]_block_invoke ()

#3 0x00007fffb2bf68a6 in -[QLSeamlessDocumentCloser closeWindow:contentFrame:withBlock:] ()

#4 0x00007fffaa98dfb9 in -[NSWindow _close] ()

#5 0x00007fffaaa5e999 in -[NSDocument _something:didSomething:soContinue:] ()

#6 0x00007fffaaa5d2d1 in __75-[NSDocument canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo:]_block_invoke ()

#7 0x00007fffaacce5ed in -[NSDocument(NSDocumentSerializationAPIs) performActivityWithSynchronousWaiting:usingBlock:cancellationHandler:] ()

#8 0x00007fffaaa5d1db in -[NSDocument canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo:] ()

#9 0x00007fffaaa1cbb5 in -[NSWindow __close] ()

Regarding whether anything in the NSDocument gets set as the delegate of a property: I'm not exactly sure what this question means; so forgive me, I'm not being pedantic when I ask (I'm just so bewildered by this error, I want to make sure I'm not missing anything)—how can I have my NSDocument subclass be the delegate of a property? My NSDocument subclass is the delegate of an NSWindow, and an NSTableView subclass. Both of these are set in the xib. There are no delegates that are declared as assign in my code that I can see.


Based on the rest of your message, and something Quincey suggested above, I tried the following in in the dealloc method of my NSDocument subclass:


[(NSWindow *)[[self windowControllers] objectAtIndex:0] setDelegate:nil];


This seems to make the problem go away, and be (mostly) correct behaviour. So it does seems that your supposition that an object is outliving its delegate may be the source of the problem. From this, I'm guessing my code is sort of a hack: it seems my NSWindow is sending a dealloc message to its delegate (my NSDocument subclass), and by setting the delegate of the window to nil, I avoid this behaviour. This doesn't really solve the problem outright, but it does avoid the crash. I also say "mostly" because the following gets logged when any document is closed:


**** [NSAutoreleasePool drain]: This pool has already been drained, do not release it (double release).


Any suggestions about how to solve this last part? It seems like I've maybe chased the problem into a corner at least. Thanks for your help.

Nothing really helpful here. It just makes things look more normal.


I've set a breakpoint at -[NSApplication(NSWindowCache) _checkForTerminateAfterLastWindowClosed:saveWindows:] in a document app, and looked at at the disassembly of this method. The result is quite interesting.


AppKit`-[NSApplication(NSWindowCache) _checkForTerminateAfterLastWindowClosed:saveWindows:]:
-> 0x7fffc832ac13 <+0>: pushq %rbp
0x7fffc832ac14 <+1>: movq %rsp, %rbp
0x7fffc832ac17 <+4>: pushq %r15
0x7fffc832ac19 <+6>: pushq %r14
0x7fffc832ac1b <+8>: pushq %r12
0x7fffc832ac1d <+10>: pushq %rbx
0x7fffc832ac1e <+11>: movl %ecx, %r14d
0x7fffc832ac21 <+14>: movq %rdi, %r15
0x7fffc832ac24 <+17>: movl $0x1, %edi
0x7fffc832ac29 <+22>: xorl %esi, %esi
0x7fffc832ac2b <+24>: xorl %edx, %edx
0x7fffc832ac2d <+26>: callq 0x7fffc7fa06bd ; isWindowObjectInCache
0x7fffc832ac32 <+31>: testb %al, %al
0x7fffc832ac34 <+33>: jne 0x7fffc832aca2 ; <+143>
0x7fffc832ac36 <+35>: movq 0x1cb6a97b(%rip), %rsi ; "delegate"
0x7fffc832ac3d <+42>: movq %r15, %rdi
0x7fffc832ac40 <+45>: callq *0x1c89a512(%rip) ; (void *)0x00007fffdf326040: objc_msgSend
0x7fffc832ac46 <+51>: movq %rax, %rbx
0x7fffc832ac49 <+54>: testq %rbx, %rbx
0x7fffc832ac4c <+57>: je 0x7fffc832aca2 ; <+143>
0x7fffc832ac4e <+59>: movq 0x1cb9383b(%rip), %r12 ; "applicationShouldTerminateAfterLastWindowClosed:"
0x7fffc832ac55 <+66>: movq 0x1cb6b3dc(%rip), %rsi ; "respondsToSelector:"
0x7fffc832ac5c <+73>: movq %rbx, %rdi
0x7fffc832ac5f <+76>: movq %r12, %rdx
0x7fffc832ac62 <+79>: callq *0x1c89a4f0(%rip) ; (void *)0x00007fffdf326040: objc_msgSend


I just pasted to the point where the crashing obj_msgSend occurs. Look at the comments, and guess what it's doing. At offset +26 it checks whether there are any windows open, and if there are it skips over the rest of this. In your case, all windows are closed, so it continues. It then gets the application delegate (+45), tests whether the delegate implements "applicationShouldTerminateAfterLastWindowClosed:" (+79), and that's where it crashes.


IOW, there's every indication that your app delegate got deallocated when the last window closed. Can you check and see if that's happening?

I think I may be on the track of what is going on. It turns out that the NSDocument subclass is my App delegate. If my resaoning is correct, then what is going on is that the NSDocument subclass gets deallocated when the last document closes, which means the App delegate goes away. Does that sound right?


In order to solve this problem, would it be appropriate to set the MainMenu.xib controller as my App delegate instead of my NSDocument subclass? There is only one controller for MainMenu.xib. Or, is there some way to get the automaticalyl created NSDocumentController to do it (as seems to be suggested by this part of the guide)?

Accepted Answer

>> the NSDocument subclass gets deallocated when the last document closes […] Does that sound right?


I wouldn't have thought so, but maybe. Anyway, it sounds like a terrible idea to use it. How do you implement delegate methods? As class methods?


>> would it be appropriate to set the MainMenu.xib controller as my App delegate instead of my NSDocument subclass?


I'm not sure what object you mean by controller. The way it's usually done (usually == almost always) is to put the App delegate object into MainMenu.xib. It's just a custom subclass of NSObject that adopts NSApplicationDelegate protocol. (If you need to create it, drag in an "Object" item from the IB library, the blue cube, and change its class to match the app delegate class you define in code.) Then this custom object is connected, in the XIB, as the delegate of the NSApplication pseudo-object that's also in the XIB. That means your app delegate is created when the main menu NIB is instantiated, and never goes away. You don't need any code to set this up.


In fact, this structure is what you get from an Xcode template, you hardly ever have to set it up yourself. Maybe your app was originally created from a very old template, or someone went to the trouble of throwing away the in-XIB app delegate, or some other weird scenario.

EXC_BAD_ACCESS when closing NSDocument window (ARC enabled)
 
 
Q