-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Description
Steps to Reproduce
- Download the test case, unzip, open solution and run on an iOS device.
- Tap the hamburger menu -> Browse -> First item
Current Behavior
An Objective-C exception which crashes the application:
Objective-C exception thrown. Name: NSInvalidArgumentException Reason: -[Xamarin_Forms_Platform_iOS_ShellSectionRenderer_NavDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]: unrecognized selector sent to instance 0x123412300
Expected Behavior
No crash.
Version Used:
Xamarin.iOS master (dotnet/macios@1b09465) or 13.20.1.18 (haven't tried earlier versions).
This is mono's 2020-02 branch.
Problem
The problem is that mono's interpreter doesn't zero-extend bool arguments to fill 64-bit registers.
lldb session: https://gist.github.com/rolfbjarne/2bbd8b5971e37752b6a8098b23f9ea03
note how the x2 register isn't 0x1, but 0x0000000100000001 at the entry to the function: https://gist.github.com/rolfbjarne/2bbd8b5971e37752b6a8098b23f9ea03#file-lldb-session-L31
This is the P/Invoke in question:
[DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")]
public extern static void void_objc_msgSend_bool (IntPtr receiver, IntPtr selector, bool arg1);and it's called like something like this:
void_objc_msgSend_bool (ptr1, ptr2, true);I tried figuring out if the ARM64 spec actually requires parameters in registers to be zero-extended to fill the entire 64-bit register, and I couldn't find any supporting statement. Arm's calling convention document (https://developer.arm.com/documentation/ihi0055/c/) seems to indicate any unused bits are undefined.
Apple has a few changes to the ARM64 calling convention (https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html), one seems particularly relevant:
The general ABI specifies that it is the callee’s responsibility to sign or zero-extend arguments having fewer than 32 bits, and that unused bits in a register are unspecified. In iOS, however, the caller must perform such extensions, up to 32 bits.
but that's only to extend to 32-bit, not 64-bit.
That being said, it doesn't matter much what the spec says, what matters is what clang thinks, and clang certainly seems to think that the bool arguments should be zero-extended to 64-bits.
The following test case:
void x (BOOL b);
void x (BOOL b)
{
NSUInteger u = 0;
u |= b;
NSLog (@"u: %llx", (unsigned long long) u);
}
typedef void (y2) (NSUInteger b);
typedef void (x2) (BOOL b);
void testbool ()
{
y2* z = (y2*) x;
z (0x1111111111111111);
}prints "u: 0x1" in Debug mode and "u: 0x1111111111111111" in release mode when added to an Xcode project and run on an iOS device.