Bug 39507 - Hard crash when overriding HasKeyEquivalentForEvent in NSMenuDelegate
Summary: Hard crash when overriding HasKeyEquivalentForEvent in NSMenuDelegate
Status: CONFIRMED
Alias: None
Product: Xamarin.Mac
Classification: Desktop
Component: Runtime ()
Version: 2.8.0 (C7)
Hardware: Macintosh Mac OS
: High normal
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2016-03-10 05:12 UTC by xamarin
Modified: 2016-09-26 21:58 UTC (History)
4 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Repro in XM (350.59 KB, application/zip)
2016-03-10 14:48 UTC, Chris Hamons
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 39507 on Developer Community or GitHub if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
CONFIRMED

Description xamarin 2016-03-10 05:12:14 UTC
Created a new MonoMac project. Added a new NSMenuDelegate that overrides HasKeyEquivalentForEvent. As soon as you try a shortcut, MonoMac crashes hard in native code.

Here's the entirety of MainWindow.cs:


using System;
using System.Collections.Generic;
using System.Linq;
using MonoMac.Foundation;
using MonoMac.AppKit;

namespace Hello
{
    public partial class MainWindow : MonoMac.AppKit.NSWindow
    {
#region Constructors

        // Called when created from unmanaged code
        public MainWindow(IntPtr handle)
            : base(handle)
        {
            Initialize();
        }
		
        // Called when created directly from a XIB file
        [Export("initWithCoder:")]
        public MainWindow(NSCoder coder)
            : base(coder)
        {
            Initialize();
        }

        // Shared initialization code
        void Initialize()
        {
            var menu = NSApplication.SharedApplication.Menu;
            var menuDelegate = menu.Delegate;
            if (menuDelegate == null)
            {
                MyMenuDelegate.Me = new MyMenuDelegate();
                menu.Delegate = MyMenuDelegate.Me;
            }
        }

        private class MyMenuDelegate : NSMenuDelegate
        {
            internal static MyMenuDelegate Me { get; set; }

            #region implemented abstract members of NSMenuDelegate
            public override void MenuWillHighlightItem(NSMenu menu, NSMenuItem item)
            {
                ;
            }
            #endregion

            public override bool HasKeyEquivalentForEvent(NSMenu menu, NSEvent theEvent, NSObject target, MonoMac.ObjCRuntime.Selector action)
            {
                return false;
            }
        }
#endregion
    }
}


The crash stack is this:

Loaded assembly: /Users/steveno/Projects/Hello/Hello/bin/Debug/Hello.app/Contents/MonoBundle/Hello.exe
Loaded assembly: /Users/steveno/Projects/Hello/Hello/bin/Debug/Hello.app/Contents/MonoBundle/MonoMac.dll [External]
Loaded assembly: /Library/Frameworks/Mono.framework/Versions/4.0.0/lib/mono/gac/System.Core/4.0.0.0__b77a5c561934e089/System.Core.dll [External]
Loaded assembly: ObjCImplementations [External]
Loaded assembly: /Library/Frameworks/Mono.framework/Versions/4.0.0/lib/mono/gac/Mono.Security/4.0.0.0__0738eb9f132ed756/Mono.Security.dll [External]
Loaded assembly: /Library/Frameworks/Mono.framework/Versions/4.0.0/lib/mono/gac/System.Drawing/4.0.0.0__b03f5f7f11d50a3a/System.Drawing.dll [External]
Loaded assembly: /Library/Frameworks/Mono.framework/Versions/4.0.0/lib/mono/gac/System/4.0.0.0__b77a5c561934e089/System.dll [External]
Resolved pending breakpoint at 'MainWindow.cs:40,1' to void Hello.MainWindow.Initialize () [0x0002f].
Resolved pending breakpoint at 'MainWindow.cs:57,30' to bool Hello.MainWindow.MyMenuDelegate.HasKeyEquivalentForEvent (MonoMac.AppKit.NSMenu menu, MonoMac.AppKit.NSEvent theEvent, MonoMac.Foundation.NSObject target, MonoMac.ObjCRuntime.Selector action) [0x00001].
Stacktrace:

  at <unknown> <0xffffffff>
  at (wrapper managed-to-native) MonoMac.ObjCRuntime.Messaging.intptr_objc_msgSend (intptr,intptr) <IL 0x0002e, 0xffffffff>
  at MonoMac.ObjCRuntime.Runtime.GetNSObject (intptr) <IL 0x00070, 0x00223>
  at MonoMac.ObjCRuntime.NSObjectMarshaler`1.MarshalNativeToManaged (intptr) <IL 0x00002, 0x0002f>
  at (wrapper native-to-managed) object.[Hello.MainWindow+MyMenuDelegate.Boolean HasKeyEquivalentForEvent(MonoMac.AppKit.NSMenu, MonoMac.AppKit.NSEvent, MonoMac.Foundation.NSObject, MonoMac.ObjCRuntime.Selector)] (MonoMac.Foundation.NSObject,MonoMac.ObjCRuntime.Selector,MonoMac.AppKit.NSMenu,MonoMac.AppKit.NSEvent,MonoMac.Foundation.NSObject,MonoMac.ObjCRuntime.Selector) <IL 0x0016c, 0xffffffff>
  at <unknown> <0xffffffff>
  at (wrapper managed-to-native) MonoMac.AppKit.NSApplication.NSApplicationMain (int,string[]) <IL 0x000a5, 0xffffffff>
  at MonoMac.AppKit.NSApplication.Main (string[]) <IL 0x00041, 0x00107>
  at Hello.MainClass.Main (string[]) [0x00007] in /Users/steveno/Projects/Hello/Hello/Main.cs:14
  at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00060, 0xffffffff>

Native stacktrace:


Debug info from gdb:

Attaching to process 21355.
Reading symbols for shared libraries . done
Reading symbols for shared libraries ................................................................................................................................................................................................................ done
0x980c9095 in __wait4 ()
  7                                 0x980c8b3e in recvfrom$UNIX2003 ()
  6                                 0x980c90ee in __workq_kernreturn ()
  5                                 0x980c90ee in __workq_kernreturn ()
  4                                 0x980c90ee in __workq_kernreturn ()
  3 "com.apple.libdispatch-manager" 0x980c99ae in kevent ()
  2                                 0x980c680a in semaphore_wait_trap ()
* 1 "com.apple.main-thread"         0x980c9095 in __wait4 ()

Thread 7 (process 21355):
#0  0x980c8b3e in recvfrom$UNIX2003 ()
#1  0x981d6bb3 in recv$UNIX2003 ()
#2  0x004f3a68 in socket_transport_recv () at debugger-agent.c:1144
#3  0x004e51a7 in debugger_thread () at debugger-agent.c:1527
#4  0x00627560 in inner_start_thread (arg=<value temporarily unavailable, due to optimizations>) at mono-threads-posix.c:92
#5  0x0064c0dd in GC_start_routine (arg=0x14c1f60) at pthread_support.c:1502
#6  0x9814c5b7 in _pthread_start ()
#7  0x98136dce in thread_start ()

Thread 6 (process 21355):
#0  0x980c90ee in __workq_kernreturn ()
#1  0x9814f0ac in _pthread_workq_return ()
#2  0x9814ee79 in _pthread_wqthread ()
#3  0x98136daa in start_wqthread ()

Thread 5 (process 21355):
#0  0x980c90ee in __workq_kernreturn ()
#1  0x9814f0ac in _pthread_workq_return ()
#2  0x9814ee79 in _pthread_wqthread ()
#3  0x98136daa in start_wqthread ()

Thread 4 (process 21355):
#0  0x980c90ee in __workq_kernreturn ()
#1  0x9814f0ac in _pthread_workq_return ()
#2  0x9814ee79 in _pthread_wqthread ()
#3  0x98136daa in start_wqthread ()

Thread 3 (process 21355):
#0  0x980c99ae in kevent ()
#1  0x92fe9c71 in _dispatch_mgr_invoke ()
#2  0x92fe97a9 in _dispatch_mgr_thread ()

Thread 2 (process 21355):
#0  0x980c680a in semaphore_wait_trap ()
#1  0x00621aaa in mono_sem_wait (sem=0x71b3b4, alertable=1) at mono-semaphore.c:103
#2  0x005cedab in finalizer_thread (unused=0x0) at gc.c:1074
#3  0x005acdd5 in start_wrapper_internal [inlined] () at :664
#4  0x005acdd5 in start_wrapper (data=<value temporarily unavailable, due to optimizations>) at threads.c:711
#5  0x00627560 in inner_start_thread (arg=<value temporarily unavailable, due to optimizations>) at mono-threads-posix.c:92
#6  0x0064c0dd in GC_start_routine (arg=0x14c1f60) at pthread_support.c:1502
#7  0x9814c5b7 in _pthread_start ()
#8  0x98136dce in thread_start ()

Thread 1 (process 21355):
#0  0x980c9095 in __wait4 ()
#1  0x981d698a in waitpid$UNIX2003 ()
#2  0x004bf74d in mono_handle_native_sigsegv (signal=11, ctx=0xf9fe0, info=0xf9fa0) at mini-exceptions.c:2347
#3  0x0050b492 in mono_arch_handle_altstack_exception (sigctx=<value temporarily unavailable, due to optimizations>, siginfo=<value temporarily unavailable, due to optimizations>, fault_addr=<value temporarily unavailable, due to optimizations>, stack_ovf=0) at exceptions-x86.c:1077
#4  0x0040c1de in mono_sigsegv_signal_handler (_info=<value temporarily unavailable, due to optimizations>, context=<value temporarily unavailable, due to optimizations>) at mini.c:6796
#5  <signal handler called>
#6  0x92574a87 in objc_msgSend ()

=================================================================
Got a SIGSEGV while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries 
used by your application.
=================================================================
Comment 1 Chris Hamons 2016-03-10 14:48:08 UTC
Created attachment 15348 [details]
Repro in XM
Comment 2 Chris Hamons 2016-03-10 14:48:30 UTC
This also blows up with a Xamarin.Mac Unified app:

https://gist.github.com/chamons/15ffc5c8c62386aea9d5

- Launch attached app.
- Hit Apple + W
- Boom

Unhandled Exception:
System.ExecutionEngineException: Invalid type encoding for parameter
  at (wrapper managed-to-native) AppKit.NSApplication:NSApplicationMain (int,string[])
  at AppKit.NSApplication.Main (System.String[] args) [0x00041] in /Users/donblas/Programming/maccore/maccore-current/maccore/src/AppKit/NSApplication.cs:94 
  at TestMenu.MainClass.Main (System.String[] args) [0x00007] in /Users/donblas/Programming/Local/TestMenu/TestMenu/Main.cs:10 
[ERROR] FATAL UNHANDLED EXCEPTION: System.ExecutionEngineException: Invalid type encoding for parameter
  at (wrapper managed-to-native) AppKit.NSApplication:NSApplicationMain (int,string[])
  at AppKit.NSApplication.Main (System.String[] args) [0x00041] in /Users/donblas/Programming/maccore/maccore-current/maccore/src/AppKit/NSApplication.cs:94 
  at TestMenu.MainClass.Main (System.String[] args) [0x00007] in /Users/donblas/Programming/Local/TestMenu/TestMenu/Main.cs:10 
Process 8670 exited with status = 1 (0x00000001)
Comment 3 Rolf Bjarne Kvinge [MSFT] 2016-03-10 15:11:16 UTC
@Chris, this is an incorrect binding.

The selector menuHasKeyEquivalent:forEvent:target:action:'s third and forth parameters are out/ref parameters (id* / SEL*), and they're bound as NSObject and Selector.
Comment 4 Miguel de Icaza [MSFT] 2016-03-10 23:08:40 UTC
Temporary work-around:

Manually use:

[Export ("menuHasKeyEquivalent:forEvent:target:action:")] 
bool MyHasKeyEquivalentForEvent (NSMenu menu, NSEvent theEvent, IntPtr target, IntPtr action); 

Then you can test if target != IntPtr.Zero, and in that case, you can do a Runtime.GetNSObject (target) and for action, you can do the same test, and then do: new Selector (action)

As for our actual API, I suspect that this could have never worked, so we probably should break the API and use IntPtr as explained above, and deal with this in the docs.
Comment 5 xamarin 2016-03-11 03:06:57 UTC
Thanks for the suggestion. Unfortunately, it doesn't quite work -- at least for me. I believe that the last two arguments need to be by reference based on the Apple docs:

target	
Return by reference the target object for the menu item that corresponds to the event. Specify nil to request the menu's target.

action	
Return by reference the action selector for the menu item that corresponds to the event.

I've had partial success using this:

[Export ("menuHasKeyEquivalent:forEvent:target:action:")] 
bool MenuHasKeyEquivalentForEvent(NSMenu menu, NSEvent theEvent, ref IntPtr target, ref IntPtr action)


Success is partial in that when the method is called, the target is 0x0 -- as would be expected when attempting to identify a shortcut. 

Now, in my app I'm using the NSMenuItem.Activated event, so it doesn't *quite* work when I try setting things up… I.e. I can dig out the target and action and send those back out. I hard-coded my test thusly:

target = DebugCommandGroup.ForceGarbageCollectCommand.MenuItem.Target.Handle;
action = DebugCommandGroup.ForceGarbageCollectCommand.MenuItem.Action.Handle;
return true;

This *ALMOST* works … in that an event handler I hooked up to Activated is called, *but* the sender is the **MENU** that was passed into MenuHasKeyEquivalentForEvent() and not the MenuItem. So, my event handler code falls apart at that point. (It digs data out of the sender, which it expects to be the MenuItem.)

I did not dig into how the Activated event handler stuff is implemented. Out of curiosity, if my menu items were explicitly using Target and Action, would it have worked as expected?
Comment 6 Chris Hamons 2016-03-11 14:00:12 UTC
@Steve - Turns out this binding issue is a tad more complicated than @Miguel's first stab. Let us get back to you shortly.
Comment 7 Chris Hamons 2016-03-15 14:36:02 UTC
Ok, so this work around works for me:

[Export ("menuHasKeyEquivalent:forEvent:target:action:")]
public bool HasKeyEquivalentForEvent(NSMenu menu, NSEvent theEvent, IntPtr target, IntPtr action)
{
	Marshal.WriteIntPtr(target, this.Handle);
	Marshal.WriteIntPtr(action, ObjCRuntime.Selector.GetHandle("LeftSide:"));
	return true;
}

[Export("LeftSide:")]
public void HandleIt(NSEvent e)
{
	Console.WriteLine("Many whelps, handle it!");
}

We're going to look into really fixing this API in the future, but it will involve some non-trivial under the hood changes, so they will be Xamarin.Mac only.