View Issue Details

IDProjectCategoryView StatusLast Update
0035702LazarusWidgetsetpublic2019-06-11 15:00
ReporterDavid JenkinsAssigned ToDmitry Boyarintsev 
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
Product Version2.0.3 (SVN)Product Build 
Target VersionFixed in Version 
Summary0035702: Macos 10.15 beta Cocoa Apps crash with mainWindow.representedURL KVO error message
DescriptionOn Macos 10.15 beta Cocoa apps crash with following message:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot update for observer <NSFrontmostDocumentWindowObserver 0x7fff8ec52208> for the key path "mainWindow.representedURL" from <TCocoaApplication 0x100d089a0>, most likely because the value for the key "mainWindow" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the TCocoaApplication class.'

Crash can occur on on startup (for our app) or also reported to happen when a two form app when opening second form as modal dialog.
Steps To ReproduceBuild app for cocoa 64 with multiple forms
Additional InformationError is caused because Cocoa currently doesn't run through base level NSApplication.run and thus appropriate ._isrunning flag is not set (notice that this also required the override of NSApplication.isRunning done for Mantis 32177)

NSFrontmostDocumentWindowObserver registers for all changes to mainWindow.representedURL. During initialization of NSApplication (down inside Cocoa code) calls to willChangeValueForKey() and didChangevalueForKey() are made to '_mainWindow' or 'mainWindow' depending on state of _isRunning flag (not from calls to .isRunning but from the memory locaiton where it is stored) and NSapplication.isActive. Because we don't go through NSApplication.run the bit for .isRunning is never set and calls to willChangevalueForKey(), didChangevalueForKey() do not happen appropriately for 'mainWindow'.

The crash occurs when _handleApplicationActivated calls removeObserverForKeyPath() on an observer object that is supposed to be set up for 'mainWindow' but it was never actually set up i.e. addObserverForKeyPath() was never called. Documentation for removeObserverForKeyPath() indicates that removing an object that hasn't been set up with addObserverForKeyPath() is an error condition. That is the condition we are hitting.

Suggested fix (patch attached) is to run through NSApplication.run first and then jump into our aloop().

Accomplished by removing TCocoaApplication.isRunning and TCocoaApplication.run methods thus allowing NSApplication.run to be called and set up NSRunloop and make first call to nextEventMatching:unitDate, etc. On first pass through nextEventMatching. (as caught by isRun = false) we grab control and call aloop.
TagsNo tags attached.
Fixed in Revision61355
LazTarget-
WidgetsetCocoa
Attached Files
  • KVOcrash.patch (1,824 bytes)
    Index: Lazarus/laz-src/lcl/interfaces/cocoa/cocoaint.pas
    ===================================================================
    --- Lazarus/laz-src/lcl/interfaces/cocoa/cocoaint.pas	(revision 24047)
    +++ Lazarus/laz-src/lcl/interfaces/cocoa/cocoaint.pas	(revision 24048)
    @@ -71,8 +71,6 @@
         OnEvent: procedure(var AEvent: NSEvent) of object;
     
         procedure dealloc; override;
    -    function isRunning: LCLObjCBoolean; override;
    -    procedure run; override;
         procedure sendEvent(theEvent: NSEvent); override;
         function nextEventMatchingMask_untilDate_inMode_dequeue(mask: NSUInteger; expiration: NSDate; mode: NSString; deqFlag: LCLObjCBoolean): NSEvent; override;
     
    @@ -401,17 +399,7 @@
       inherited dealloc;
     end;
     
    -function TCocoaApplication.isRunning: LCLObjCBoolean;
    -begin
    -  Result:=isrun;
    -end;
     
    -procedure TCocoaApplication.run;
    -begin
    -  isrun:=true;
    -  aloop();
    -end;
    -
     procedure ForwardMouseMove(app: NSApplication; theEvent: NSEvent);
     var
       w   : NSWindow;
    @@ -504,6 +492,14 @@
     var
       cb : ICommonCallback;
     begin
    +  if not isrun then begin
    +    isrun := True;
    +    Result := nil;
    +    aloop();
    +    terminate(nil);
    +    exit;
    +  end;
    +
       {$ifdef BOOLFIX}
       Result:=inherited nextEventMatchingMask_untilDate_inMode_dequeue_(
         mask,
    Index: Lazarus/laz-src/lcl/interfaces/cocoa/cocoaobject.inc
    ===================================================================
    --- Lazarus/laz-src/lcl/interfaces/cocoa/cocoaobject.inc	(revision 24047)
    +++ Lazarus/laz-src/lcl/interfaces/cocoa/cocoaobject.inc	(revision 24048)
    @@ -67,7 +67,6 @@
      ------------------------------------------------------------------------------}
     procedure TCocoaWidgetSet.AppRun(const ALoop: TApplicationMainLoop);
     begin
    -  NSApp.finishLaunching;
       if Assigned(ALoop) then
       begin
         TCocoaApplication(NSApp).aloop:=ALoop;
    
    KVOcrash.patch (1,824 bytes)

Activities

David Jenkins

2019-06-11 00:18

reporter  

KVOcrash.patch (1,824 bytes)
Index: Lazarus/laz-src/lcl/interfaces/cocoa/cocoaint.pas
===================================================================
--- Lazarus/laz-src/lcl/interfaces/cocoa/cocoaint.pas	(revision 24047)
+++ Lazarus/laz-src/lcl/interfaces/cocoa/cocoaint.pas	(revision 24048)
@@ -71,8 +71,6 @@
     OnEvent: procedure(var AEvent: NSEvent) of object;
 
     procedure dealloc; override;
-    function isRunning: LCLObjCBoolean; override;
-    procedure run; override;
     procedure sendEvent(theEvent: NSEvent); override;
     function nextEventMatchingMask_untilDate_inMode_dequeue(mask: NSUInteger; expiration: NSDate; mode: NSString; deqFlag: LCLObjCBoolean): NSEvent; override;
 
@@ -401,17 +399,7 @@
   inherited dealloc;
 end;
 
-function TCocoaApplication.isRunning: LCLObjCBoolean;
-begin
-  Result:=isrun;
-end;
 
-procedure TCocoaApplication.run;
-begin
-  isrun:=true;
-  aloop();
-end;
-
 procedure ForwardMouseMove(app: NSApplication; theEvent: NSEvent);
 var
   w   : NSWindow;
@@ -504,6 +492,14 @@
 var
   cb : ICommonCallback;
 begin
+  if not isrun then begin
+    isrun := True;
+    Result := nil;
+    aloop();
+    terminate(nil);
+    exit;
+  end;
+
   {$ifdef BOOLFIX}
   Result:=inherited nextEventMatchingMask_untilDate_inMode_dequeue_(
     mask,
Index: Lazarus/laz-src/lcl/interfaces/cocoa/cocoaobject.inc
===================================================================
--- Lazarus/laz-src/lcl/interfaces/cocoa/cocoaobject.inc	(revision 24047)
+++ Lazarus/laz-src/lcl/interfaces/cocoa/cocoaobject.inc	(revision 24048)
@@ -67,7 +67,6 @@
  ------------------------------------------------------------------------------}
 procedure TCocoaWidgetSet.AppRun(const ALoop: TApplicationMainLoop);
 begin
-  NSApp.finishLaunching;
   if Assigned(ALoop) then
   begin
     TCocoaApplication(NSApp).aloop:=ALoop;
KVOcrash.patch (1,824 bytes)

Dmitry Boyarintsev

2019-06-11 15:00

developer   ~0116677

applied with insignificant changes.
please test and close if ok

Issue History

Date Modified Username Field Change
2019-06-11 00:18 David Jenkins New Issue
2019-06-11 00:18 David Jenkins File Added: KVOcrash.patch
2019-06-11 15:00 Dmitry Boyarintsev Assigned To => Dmitry Boyarintsev
2019-06-11 15:00 Dmitry Boyarintsev Status new => resolved
2019-06-11 15:00 Dmitry Boyarintsev Resolution open => fixed
2019-06-11 15:00 Dmitry Boyarintsev Fixed in Revision => 61355
2019-06-11 15:00 Dmitry Boyarintsev LazTarget => -
2019-06-11 15:00 Dmitry Boyarintsev Widgetset Cocoa => Cocoa
2019-06-11 15:00 Dmitry Boyarintsev Note Added: 0116677