gnustep / libs-gui

The GNUstep gui library is a library of graphical user interface classes written completely in the Objective-C language; the classes are based upon Apple's Cocoa framework (which came from the OpenStep specification). *** Larger patches require copyright assignment to FSF. please file bugs here. ***
http://www.gnustep.org
GNU General Public License v3.0
276 stars 100 forks source link

NSTextView can cause a crash if its delegate has been deallocated earlier #245

Closed svgol closed 5 months ago

svgol commented 6 months ago

I have an app that downloads a media file and creates a window to show progress of downloading in an NSTextView (the output of the tool ytdl used to download the file). It creates the window with -[initWithWindowNibName:] of a NSWindowController's descendant. When downloading is done the descendant receives -[close] and then it is autoreleased in its -[windowWillClose:]. The descendant is a delegate of the NSTextView. Sometimes it crashes with the following stacktrace:

0 0x00007ffff7f9b875 in objc_msg_lookup (receiver=0x555556026c30, op=0x7ffff7479a30 <_OBJC_SELECTOR_TABLE+80>) at /usr/src/debug/gcc/gcc/libobjc/sendmsg.c:442

1 0x00007ffff711305e in -[NSNotificationCenter removeObserver:name:object:] (self=0x555555778480, _cmd=0x7ffff7dc22a0 <_OBJC_SELECTOR_TABLE+608>,

observer=0x555556026c30, name=0x0, object=0x5555558fc6e0) at NSNotificationCenter.m:1052

2 0x00007ffff7a7e661 in -[NSTextView dealloc] (self=0x5555558fc6e0, _cmd=0x7ffff7484670 <_OBJC_SELECTOR_TABLE+48>) at NSTextView.m:1144

3 0x00007ffff71222e7 in objc_release_fast_np_internal (anObject=0x5555558fc6e0) at NSObject.m:568

4 0x00007ffff7122306 in release_fast (anObject=0x5555558fc6e0) at NSObject.m:582

5 0x00007ffff712426f in -[NSObject release] (self=0x5555558fc6e0, _cmd=0x7ffff74a77d0 <_OBJC_SELECTOR_TABLE+80>) at NSObject.m:2079

6 0x00007ffff7174736 in -[GSRunLoopPerformer dealloc] (self=0x555555bb44c0, _cmd=0x7ffff7484670 <_OBJC_SELECTOR_TABLE+48>) at NSRunLoop.m:113

7 0x00007ffff71222e7 in objc_release_fast_np_internal (anObject=0x555555bb44c0) at NSObject.m:568

8 0x00007ffff7122306 in release_fast (anObject=0x555555bb44c0) at NSObject.m:582

9 0x00007ffff712426f in -[NSObject release] (self=0x555555bb44c0, _cmd=0x7ffff74a77d0 <_OBJC_SELECTOR_TABLE+80>) at NSObject.m:2079

10 0x00007ffff7175bf7 in -[NSRunLoop(Private) _checkPerformers:] (self=0x5555557cc020, _cmd=0x7ffff74a7bd0 <_OBJC_SELECTOR_TABLE+1104>, context=0x5555557aed60)

at NSRunLoop.m:547

11 0x00007ffff7177874 in -[NSRunLoop acceptInputForMode:beforeDate:] (self=0x5555557cc020, _cmd=0x7ffff74a7c50 <_OBJC_SELECTOR_TABLE+1232>,

mode=0x7ffff74a6a00 <_OBJC_INSTANCE_2>, limit_date=0x555555d9e270) at NSRunLoop.m:1187

12 0x00007ffff717819c in -[NSRunLoop runMode:beforeDate:] (self=0x5555557cc020, _cmd=0x7ffff7df4b20 <_OBJC_SELECTOR_TABLE+768>,

mode=0x7ffff74a6a00 <_OBJC_INSTANCE_2>, date=0x555555815f90) at NSRunLoop.m:1334

13 0x00007ffff7af3777 in -[GSDisplayServer(EventOps) getEventMatchingMask:beforeDate:inMode:dequeue:] (self=0x5555557f4060,

_cmd=0x7ffff2e2a1b0 <_OBJC_SELECTOR_TABLE+48>, mask=4294967295, limit=0x555555815f90, mode=0x7ffff74a6a00 <_OBJC_INSTANCE_2>, flag=1 '\001')
at GSDisplayServer.m:1039

14 0x00007ffff2ddca21 in -[XGServer(X11Ops) getEventMatchingMask:beforeDate:inMode:dequeue:] (self=0x5555557f4060, _cmd=0x7ffff7c8e4b0 <_OBJC_SELECTOR_TABLE+1648>,

mask=4294967295, limit=0x555555815f90, mode=0x7ffff74a6a00 <_OBJC_INSTANCE_2>, flag=1 '\001') at XGServerEvent.m:2743

15 0x00007ffff787e86d in DPSGetEvent (ctxt=0x5555557f4060, mask=4294967295, limit=0x555555815f90, mode=0x7ffff74a6a00 <_OBJC_INSTANCE_2>)

at ../Headers/Additions/GNUstepGUI/GSDisplayServer.h:203

16 0x00007ffff7885cc6 in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] (self=0x55555586fa00, _cmd=0x7ffff7c8e9b0 <_OBJC_SELECTOR_TABLE+2928>,

mask=4294967295, expiration=0x555555815f90, mode=0x7ffff74a6a00 <_OBJC_INSTANCE_2>, flag=1 '\001') at NSApplication.m:2205

17 0x00007ffff7884002 in -[NSApplication run] (self=0x55555586fa00, _cmd=0x7ffff7c79150 <_OBJC_SELECTOR_TABLE+1904>) at NSApplication.m:1582

18 0x00007ffff785bc1a in NSApplicationMain (argc=1, argv=0x7fffffffe198) at Functions.m:119

19 0x00005555555589b5 in main (argc=1, argv=0x7fffffffe198) at CrashAfterWinControllerDeallocation_main.m:28

The cause is that conditions exist allowing NSTextView is deallocated AFTER its delegate. NSTextView registers itself in NSRunLoop as a performer to receive periodic updates. This can lead to -[NSTextView dealloc] is called when the delegate has gone and the following chunk:

 if (_delegate)
          {
               [notificationCenter removeObserver: _delegate
                            name: nil
                            object: _notifObject];
          }

causes crashes. Adding the following snippet to -[NSWindowController dealloc] can guard from crashes (provided lib-base fixes a related issue around NSNotificationCenter.m:1050):

// just in case someone implicitly subscribed the Window Controller
  [[NSNotificationCenter defaultCenter]
    removeObserver: self];

Also I believe that changing a state of another objects must not belong to any -dealloc (except for very rare and well documented cases) so the above mentioned chunk in -[NSTextView dealloc] should be removed.

svgol commented 6 months ago

The attached app contains two meaningful methods -[AppDelegate applicationDidFinishLaunching:] where the dynamic GUI is created 10 times and -[MainWindowController windowWillClose:] where the WindowController is autoreleased.

Uncomment the line MainWindowController.m:16

//[textView setDelegate: nil]; // libs-gui doesn't do it... BUG???

and no crash happens.

CrashAfterWinControllerDeallocation.subproj.tar.gz

svgol commented 6 months ago

The libs-base issue this issue depends on

svgol commented 6 months ago

The crash has gone with the #gnustep/libs-base/issues/381 is fixed.

fredkiefer commented 5 months ago

Closing, as it was fixed in base. I will still add one line to gui setting the delegate to nil, just to make sure.