While reading objc_msg_lookup_sender (Source sendmsg2.c) I noticed something in the nil-receiver handling that I wanted to check with you before treating it as a bug, since this lookup code is subtle and I may be missing an intended reason.
When the receiver is nil, the code selects a "zero slot" whose IMP returns the correctly-typed zero based on the method's return type:
const char *t = selector->types;
// Skip type qualifiers
while ('r' == *t || 'n' == *t || 'N' == *t || 'o' == *t ||
'O' == *t || 'R' == *t || 'V' == *t || 'A' == *t)
{
t++;
}
switch (selector->types[0])
{
case 'D': return &nil_slot_D_v1;
case 'd': return &nil_slot_d_v1;
case 'f': return &nil_slot_f_v1;
}
return &nil_slot_v1;
The loop advances t past any leading type qualifiers, but the switch then reads selector->types[0] (the un-skipped first character), so t is computed and then discarded. For an unqualified return type the two are the same character, so it usually doesn't matter — but if the return type carries a qualifier prefix, the switch sees the qualifier rather than the type char and falls through to nil_slot_v1.
Is the intent to switch on the post-skip character (*t), or is there a reason the qualifier is deliberately kept here?
The reason it can matter in practice: clang does emit qualifier-prefixed encodings for floating-point return types carrying a bycopy / in / _Atomic qualifier:
- (double) → d16@0:8
- (bycopy double) → Od16@0:8
- (_Atomic double) → Ad16@0:8
For such a method sent to nil through objc_msg_lookup_sender, the returned slot is nil_slot_v1 (IMP nil_method, whose signature is long long) instead of nil_slot_d_v1 (IMP nil_method_d). Since nil_method never writes the floating-point return register, [nil someBycopyDoubleMethod] comes back as a garbage double rather than 0.0.
Small reproduction, built against master:
@interface Bar : NSObject
- (double) plainD;
- (bycopy double) bcD;
@end
@implementation Bar
- (double) plainD { return 1.0; }
- (bycopy double) bcD { return 1.0; }
@end
id nilRcv = nil;
struct objc_slot *sp = objc_msg_lookup_sender(&nilRcv,
method_getName(class_getInstanceMethod([Bar class], @selector(plainD))), nil);
struct objc_slot *sb = objc_msg_lookup_sender(&nilRcv,
method_getName(class_getInstanceMethod([Bar class], @selector(bcD))), nil);
gives
plainD d16@0:8 -> nil-slot IMP = nil_method_d
bcD Od16@0:8 -> nil-slot IMP = nil_method (different)
I appreciate this only bites for a fairly narrow combination — a qualifier-prefixed floating-point return, sent to nil, via the slot-based lookup (not the objc_msgSend fast path) — so it may be below the threshold you'd want to change. But since the qualifier skip looks like it was meant to feed the switch, I wanted to flag it and ask whether you'd like the three nil-return switches to use switch (*t), or whether it's written this way deliberately.
Thanks!
cc @davidchisnall
While reading
objc_msg_lookup_sender(Sourcesendmsg2.c) I noticed something in the nil-receiver handling that I wanted to check with you before treating it as a bug, since this lookup code is subtle and I may be missing an intended reason.When the receiver is nil, the code selects a "zero slot" whose IMP returns the correctly-typed zero based on the method's return type:
The loop advances
tpast any leading type qualifiers, but theswitchthen readsselector->types[0](the un-skipped first character), sotis computed and then discarded. For an unqualified return type the two are the same character, so it usually doesn't matter — but if the return type carries a qualifier prefix, the switch sees the qualifier rather than the type char and falls through tonil_slot_v1.Is the intent to switch on the post-skip character (
*t), or is there a reason the qualifier is deliberately kept here?The reason it can matter in practice: clang does emit qualifier-prefixed encodings for floating-point return types carrying a
bycopy/in/_Atomicqualifier:- (double)→d16@0:8- (bycopy double)→Od16@0:8- (_Atomic double)→Ad16@0:8For such a method sent to nil through
objc_msg_lookup_sender, the returned slot isnil_slot_v1(IMPnil_method, whose signature islong long) instead ofnil_slot_d_v1(IMPnil_method_d). Sincenil_methodnever writes the floating-point return register,[nil someBycopyDoubleMethod]comes back as a garbagedoublerather than0.0.Small reproduction, built against master:
gives
I appreciate this only bites for a fairly narrow combination — a qualifier-prefixed floating-point return, sent to nil, via the slot-based lookup (not the
objc_msgSendfast path) — so it may be below the threshold you'd want to change. But since the qualifier skip looks like it was meant to feed the switch, I wanted to flag it and ask whether you'd like the three nil-return switches to useswitch (*t), or whether it's written this way deliberately.Thanks!
cc @davidchisnall