From fe9dc7a6b384c24d793e4fbda6ba0eb7fa751e49 Mon Sep 17 00:00:00 2001 From: Todd White Date: Sat, 18 Jul 2026 21:51:33 -0400 Subject: [PATCH] Skip type qualifiers before selecting the nil return slot The nil-receiver fast paths in objc_msg_lookup_sender, objc_slot_lookup and objc_slot_lookup_version skip type qualifiers into t but then switched on selector->types[0], the unskipped encoding. A qualifier-prefixed floating point return (for example bycopy double, encoded "Od...") fell through to the integer nil slot, so a message to nil returned an undefined floating point value. Switch on *t instead. --- Test/CMakeLists.txt | 1 + Test/NilTypeQualifier.m | 64 +++++++++++++++++++++++++++++++++++++++++ sendmsg2.c | 6 ++-- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 Test/NilTypeQualifier.m diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 8d53953d..b88dda39 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -43,6 +43,7 @@ set(TESTS IVarOverlap.m IVarSuperclassOverlap.m objc_msgSend.m + NilTypeQualifier.m msgInterpose.m NilException.m MethodArguments.m diff --git a/Test/NilTypeQualifier.m b/Test/NilTypeQualifier.m new file mode 100644 index 00000000..bc2c263c --- /dev/null +++ b/Test/NilTypeQualifier.m @@ -0,0 +1,64 @@ +#include "Test.h" +#include "../objc/slot.h" +#include + +@interface QualReturn : Test +- (double)d; +- (bycopy double)bcd; +- (float)f; +- (bycopy float)bcf; +@end +@implementation QualReturn +- (double)d { return 1.0; } +- (bycopy double)bcd { return 1.0; } +- (float)f { return 1.0f; } +- (bycopy float)bcf { return 1.0f; } +@end + +/* The nil-receiver fast paths in the runtime lookup functions pick a zero slot + * from the method's return type, so a message to nil returns a correctly typed + * zero. The return type is read after skipping any type qualifiers (bycopy, + * oneway, ...). A qualifier-prefixed floating-point return (e.g. bycopy + * double, encoded "Od...") must map to the same zero slot as the plain + * floating-point return; otherwise nil gets an integer zero slot and the + * floating-point result is undefined. + */ + +static IMP nilSlotSender(SEL sel) +{ + id nilObj = nil; + return objc_msg_lookup_sender(&nilObj, sel, nil)->method; +} + +static IMP nilSlotLookup2(SEL sel) +{ + id nilObj = nil; + return objc_msg_lookup2(&nilObj, sel); +} + +int main(void) +{ + Class cls = [QualReturn class]; + Method mD = class_getInstanceMethod(cls, @selector(d)); + Method mBcD = class_getInstanceMethod(cls, @selector(bcd)); + Method mF = class_getInstanceMethod(cls, @selector(f)); + Method mBcF = class_getInstanceMethod(cls, @selector(bcf)); + SEL d = method_getName(mD); + SEL bcd = method_getName(mBcD); + SEL f = method_getName(mF); + SEL bcf = method_getName(mBcF); + + /* Premise: the bycopy qualifier prefixes the encoding, so the return type + * character is not at offset 0. */ + assert('O' == method_getTypeEncoding(mBcD)[0]); + assert('O' == method_getTypeEncoding(mBcF)[0]); + + /* A qualifier-prefixed floating-point return must select the same nil slot + * as the unqualified one. */ + assert(nilSlotSender(bcd) == nilSlotSender(d)); + assert(nilSlotSender(bcf) == nilSlotSender(f)); + assert(nilSlotLookup2(bcd) == nilSlotLookup2(d)); + assert(nilSlotLookup2(bcf) == nilSlotLookup2(f)); + + return 0; +} diff --git a/sendmsg2.c b/sendmsg2.c index 4ffdebd2..60ff2cfa 100644 --- a/sendmsg2.c +++ b/sendmsg2.c @@ -189,7 +189,7 @@ struct objc_slot *objc_msg_lookup_sender(id *receiver, SEL selector, id sender) { t++; } - switch (selector->types[0]) + switch (*t) { case 'D': return &nil_slot_D_v1; case 'd': return &nil_slot_d_v1; @@ -225,7 +225,7 @@ static struct objc_slot2* objc_slot_lookup(id *receiver, SEL selector) { t++; } - switch (selector->types[0]) + switch (*t) { case 'D': return (struct objc_slot2*)&nil_slot_D; case 'd': return (struct objc_slot2*)&nil_slot_d; @@ -261,7 +261,7 @@ struct objc_slot2 *objc_slot_lookup_version(id *receiver, SEL selector, uint64_t { t++; } - switch (selector->types[0]) + switch (*t) { case 'D': return (struct objc_slot2*)&nil_slot_D; case 'd': return (struct objc_slot2*)&nil_slot_d;