Software engineer, London UK

Why you should use -Wstrict-selector-match

Sep 28 2013

This morning I’ve read an interesting post on Oliver Drobnik’s blog and since answering properly on Twitter is kind of hard I decided to write a post about my understanding of the issue.

In a nutshell, his post shows how invoking the length method on an object conforming to the UILayoutSupport protocol might not return what one would expect if the reference is typed as id.
The reason for this is that the compiler doesn’t have enough information about the type of the object this method will be invoked onto and it needs to assume things by picking a method signature to set up the calling environment. In this case, it will likely pick the length method on NSString which happens to return an NSUInteger. However, in the UILayoutSupport case, the length method returns a CGFloat.

Now, you might think this is not a big deal but it actually is and let’s try and figure why 2 very distinct numbers are returned when using the correctly typed reference or not.

Since I have a much better understanding of the x86-64 assembly, ABI and calling conventions than I have of ARM or i386, I will try and illustrate the issue in a little Cocoa application. Luckily AppKit has a similar CGFloat returning length function in NSStatusItem that we can use to reproduce a similar issue (NSString is itself available in Foundation in both iOS and OS X).

Let’s assume we have the following program:

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
    NSStatusItem *statusItem = [[NSStatusItem alloc] init];
    [statusItem setLength:60.0];
    
    CGFloat correctLength = [self _checkCorrectLength:statusItem];
    CGFloat wrongLength = [self _checkWrongLength:statusItem];
    
    NSParameterAssert(correctLength == wrongLength);
}

- (CGFloat)_checkCorrectLength:(NSStatusItem *)statusItem
{
    return [statusItem length];
}

- (CGFloat)_checkWrongLength:(id)statusItem
{
    return [statusItem length];
}

@end

This is a simple enough program and if we run it we will fail on the assertion that 60 is not equal to 16. No matter what value we set for the length, the incorrect value will always be 16.

Similarly as what Oliver describes in his post, we have here two different cases. In the _checkCorrectLength, the first argument is correctly typed as a reference to an NSStatusItem object on which the length method is later invoked on. In the the _checkWrongLength, the argument is typed as id and when the length method is invoked, we let the compiler figure things out on its own.

Before diving into the assembly generated by the compiler for our own code, let’s disassemble the length function in NSStatusItem.

AppKit`-[NSStatusItem length]:
0x7fff906bd264:  pushq  %rbp
0x7fff906bd265:  movq   %rsp, %rbp
0x7fff906bd268:  movq   -442811119(%rip), %rax    ; NSStatusItem._fLength
0x7fff906bd26f:  movsd  (%rdi,%rax), %xmm0
0x7fff906bd274:  popq   %rbp
0x7fff906bd275:  ret

Before explaining what is going on, it is worth having a quick look at the NSStatusItem instance variables structure.

@interface NSStatusItem : NSObject
{
 @private
    NSStatusBar* _fStatusBar;
    CGFloat      _fLength;
    NSWindow*    _fWindow;
    NSView*      _fView;
    int          _fPriority;
    etc...
}

Coming back to the disassembly of -[NSStatusItem length], we can see that it is pretty simple. The offset of the _fLength iVar in the class struct is moved into the %rax register. At this stage it is worth noting that %rdi still contains the receiver hence the NSStatusItem object reference. Further on, the value at the address object pointer + iVar offset is dereferenced into the %xmm0 register, which is a register used for floating point calculations. The stack is cleaned up and the function then returns.

At this point, it is very important to note that, as per the x86-64 ABI, the %xmm0 register is documented as used to pass and return floating point arguments. In the usual case where an integer argument is returned the %rax is used to return values from a function.

If you are used into looking at disassembly you might have figured out what went wrong by now but for sake of completeness let’s have a look at the disassembly of the _checkCorrectLength: and _checkWrongLength methods.

Reg`-[AppDelegate _checkCorrectLength:] at AppDelegate.m:26:
0x100001420:  pushq  %rbp
0x100001421:  movq   %rsp, %rbp
0x100001424:  subq   $32, %rsp
0x100001428:  leaq   5281(%rip), %rax
0x10000142f:  movq   %rdi, -8(%rbp)
0x100001433:  movq   %rsi, -16(%rbp)
0x100001437:  movq   %rdx, -24(%rbp)
0x10000143b:  movq   -24(%rbp), %rdx
0x10000143f:  movq   %rdx, %rdi
0x100001442:  movq   %rax, %rsi
0x100001445:  callq  *5253(%rip)
0x10000144b:  addq   $32, %rsp
0x10000144f:  popq   %rbp
0x100001450:  ret
Reg`-[AppDelegate _checkWrongLength:] at AppDelegate.m:31:
0x100001460:  pushq  %rbp
0x100001461:  movq   %rsp, %rbp
0x100001464:  subq   $48, %rsp
0x100001468:  movq   %rdi, -8(%rbp)
0x10000146c:  movq   %rsi, -16(%rbp)
0x100001470:  movq   %rdx, -24(%rbp)
0x100001474:  movq   5205(%rip), %rsi
0x10000147b:  leaq   5198(%rip), %rdi
0x100001482:  movq   %rdi, -32(%rbp)
0x100001486:  movq   %rdx, %rdi
0x100001489:  movq   -32(%rbp), %rdx
0x10000148d:  movq   %rsi, -40(%rbp)
0x100001491:  movq   %rdx, %rsi
0x100001494:  movq   -40(%rbp), %rax
0x100001498:  callq  *%rax
0x10000149a:  movd   %rax, %xmm0
0x10000149f:  movaps 218(%rip), %xmm1
0x1000014a6:  punpckldq %xmm1, %xmm0
0x1000014aa:  movapd 222(%rip), %xmm1
0x1000014b2:  subpd  %xmm1, %xmm0
0x1000014b6:  haddpd %xmm0, %xmm0
0x1000014ba:  addq   $48, %rsp
0x1000014be:  popq   %rbp
0x1000014bf:  ret

Keep in mind that both methods are returning a CGFloat so the the %xmm0 register will be used to hold the return (floating point) argument. Also, in both method implementations, the callq instruction is a call to objc_msgSend with the object reference and the length selector.

In the correct case, we can see that upon return from the objc_msgSend function call for length, the stack is quickly cleaned up and the method implementation returns. Since the %xmm0 register hasn’t been touched, upon return of the method implementation it will still contain the value that was stored in the length method implementation.

In the incorrect case, we can clearly see that the compiler has emitted code as if the length method implementation returned an integer, by retrieving the return value in the %rax register. Since the _checkWrongLength is supposed to return a floating point, the value is then moved to the %xmm0 register and after a few instructions I honestly don’t have the knowledge to understand and a bit of stack cleanup, the function returns. So basically, the function moved the value stored by the length implementation into %rax into %xmm0 and returned.

If you followed up to here you have the answer: the value returned by the length method on an NSStatusItem object reference typed to id is the offset of the _fLength instance variable in the NSStatusItem class structure.

In your case Oliver, I also suspect 97 to be the offset of some instance variable into some layout guide object class structure. However, it's also important to note that this would be completely coincidental. %rax (or its counterpart in ARM) is not preserved across function calls and for the case of a function returning a floating point argument (and thus writing its return value in xmm0 or xmm1, or their ARM counterpart) %rax could contain any garbage.

There is a very easy way to avoid such issues by trusting the compiler and enabling the -Wstrict-selector-match warning. The compiler will warn you that it cannot make a smart decision about it and will likely result in some unexpected behavior.

blog comments powered by Disqus