darling-objc4/test/ivarSlide.m
2022-03-31 21:15:07 -07:00

506 lines
14 KiB
Objective-C

/*
TEST_BUILD
$C{COMPILE} -fobjc-weak $DIR/ivarSlide1.m $DIR/ivarSlide.m -o ivarSlide.exe
END
*/
#include "test.h"
#include <string.h>
#include <stdint.h>
#include <objc/objc-runtime.h>
#include <objc/objc-auto.h>
// fixme should check ARC layout handling
// current test checks GC layout handling which is dead
#define FIXME_CHECK_ARC_LAYOUTS 0
// ARC doesn't like __strong void* or __weak void*
#define gc_weak
#define gc_strong
#define OLD 1
#include "ivarSlide.h"
#define ustrcmp(a, b) strcmp((char *)a, (char *)b)
// Aliasing-friendly way to read from a fixed offset in an object.
uintptr_t readWord(id obj, int offset) {
uintptr_t value;
char *ptr = (char *)(__bridge void*)obj;
memcpy(&value, ptr + offset * sizeof(uintptr_t), sizeof(uintptr_t));
return value;
}
#ifdef __cplusplus
class CXX {
public:
static uintptr_t count;
uintptr_t magic;
CXX() : magic(1) { }
~CXX() { count += magic; }
};
uintptr_t CXX::count;
#endif
@interface Bitfields : Super {
uint8_t uint8_ivar;
uint8_t uint8_bitfield1 :7;
uint8_t uint8_bitfield2 :1;
id id_ivar;
uintptr_t uintptr_ivar;
uintptr_t /*uintptr_bitfield1*/ :31; // anonymous (rdar://5723893)
uintptr_t uintptr_bitfield2 :1;
id id_ivar2;
}
@end
@implementation Bitfields @end
@interface Sub : Super {
@public
uintptr_t subIvar;
gc_strong void* subIvar2;
gc_weak void* subIvar3;
#ifdef __cplusplus
CXX cxx;
#else
// same layout as cxx
uintptr_t cxx_magic;
#endif
}
@end
@implementation Sub @end
@interface Sub2 : ShrinkingSuper {
@public
gc_weak void* subIvar;
gc_strong void* subIvar2;
}
@end
@implementation Sub2 @end
@interface MoreStrongSub : MoreStrongSuper { id subIvar; } @end
@interface LessStrongSub : LessStrongSuper { id subIvar; } @end
@interface MoreWeakSub : MoreWeakSuper { id subIvar; } @end
@interface MoreWeak2Sub : MoreWeak2Super { id subIvar; } @end
@interface LessWeakSub : LessWeakSuper { id subIvar; } @end
@interface LessWeak2Sub : LessWeak2Super { id subIvar; } @end
@implementation MoreStrongSub @end
@implementation LessStrongSub @end
@implementation MoreWeakSub @end
@implementation MoreWeak2Sub @end
@implementation LessWeakSub @end
@implementation LessWeak2Sub @end
@interface NoGCChangeSub : NoGCChangeSuper {
@public
char subc3;
}
@end
@implementation NoGCChangeSub @end
@interface RunsOf15Sub : RunsOf15 {
@public
char sub;
}
@end
@implementation RunsOf15Sub @end
int main(int argc __attribute__((unused)), char **argv)
{
#if __has_feature(objc_arc)
testwarn("fixme check ARC layouts too");
#endif
/*
Bitfield ivars.
rdar://5723893 anonymous bitfield ivars crash when slid
rdar://5724385 bitfield ivar alignment incorrect
Compile-time layout of Bitfields:
[0 scan] isa
[1 skip] uint8_ivar, uint8_bitfield
[2 scan] id_ivar
[3 skip] uintptr_ivar
[4 skip] uintptr_bitfield
[5 scan] id_ivar2
Runtime layout of Bitfields:
[0 scan] isa
[1 skip] superIvar
[2 skip] uint8_ivar, uint8_bitfield
[3 scan] id_ivar
[4 skip] uintptr_ivar
[5 skip] uintptr_bitfield
[6 scan] id_ivar2
*/
[Bitfields class];
testassert(class_getInstanceSize([Bitfields class]) == 7*sizeof(void*));
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *bitfieldlayout;
bitfieldlayout = class_getIvarLayout([Bitfields class]);
testassert(0 == ustrcmp(bitfieldlayout, "\x01\x21\x21"));
bitfieldlayout = class_getWeakIvarLayout([Bitfields class]);
testassert(bitfieldlayout == NULL);
}
/*
Compile-time layout of Sub:
[0 scan] isa
[1 skip] subIvar
[2 scan] subIvar2
[3 weak] subIvar3
[6 skip] cxx
Runtime layout of Sub:
[0 scan] isa
[1 skip] superIvar
[2 skip] subIvar
[3 scan] subIvar2
[4 weak] subIvar3
[6 skip] cxx
Also, superIvar is only one byte, so subIvar's alignment must
be handled correctly.
fixme test more layouts
*/
Ivar ivar;
static Sub * volatile sub;
sub = [Sub new];
sub->subIvar = 10;
testassertequal(readWord(sub, 2), 10);
#ifdef __cplusplus
testassertequal(readWord(sub, 5), 1);
testassertequal(sub->cxx.magic, 1);
sub->cxx.magic++;
testassertequal(readWord(sub, 5), 2);
testassertequal(sub->cxx.magic, 2);
# if __has_feature(objc_arc)
sub = nil;
# else
[sub dealloc];
# endif
testassert(CXX::count == 2);
#endif
testassert(class_getInstanceSize([Sub class]) == 6*sizeof(void*));
ivar = class_getInstanceVariable([Sub class], "subIvar");
testassert(ivar);
testassert(2*sizeof(void*) == (size_t)ivar_getOffset(ivar));
testassert(0 == strcmp(ivar_getName(ivar), "subIvar"));
testassert(0 == strcmp(ivar_getTypeEncoding(ivar),
#if __LP64__
"Q"
#else
"L"
#endif
));
#ifdef __cplusplus
ivar = class_getInstanceVariable([Sub class], "cxx");
testassert(ivar);
#endif
ivar = class_getInstanceVariable([Super class], "superIvar");
testassert(ivar);
testassert(sizeof(void*) == (size_t)ivar_getOffset(ivar));
testassert(0 == strcmp(ivar_getName(ivar), "superIvar"));
testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "c"));
ivar = class_getInstanceVariable([Super class], "subIvar");
testassert(!ivar);
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *superlayout;
const uint8_t *sublayout;
superlayout = class_getIvarLayout([Super class]);
sublayout = class_getIvarLayout([Sub class]);
testassert(0 == ustrcmp(superlayout, "\x01\x10"));
testassert(0 == ustrcmp(sublayout, "\x01\x21\x20"));
superlayout = class_getWeakIvarLayout([Super class]);
sublayout = class_getWeakIvarLayout([Sub class]);
testassert(superlayout == NULL);
testassert(0 == ustrcmp(sublayout, "\x41\x10"));
}
/*
Shrinking superclass.
Subclass ivars do not compact, but the GC layout needs to
update, including the gap that the superclass no longer spans.
Compile-time layout of Sub2:
[0 scan] isa
[1-5 scan] superIvar
[6-10 weak] superIvar2
[11 weak] subIvar
[12 scan] subIvar2
Runtime layout of Sub2:
[0 scan] isa
[1-10 skip] was superIvar
[11 weak] subIvar
[12 scan] subIvar2
*/
Sub2 *sub2 = [Sub2 new];
sub2->subIvar = (void *)10;
testassertequal(readWord(sub2, 11), 10);
testassertequal(class_getInstanceSize([Sub2 class]), 13*sizeof(void*));
ivar = class_getInstanceVariable([Sub2 class], "subIvar");
testassert(ivar);
testassertequal(11*sizeof(void*), (size_t)ivar_getOffset(ivar));
testassert(0 == strcmp(ivar_getName(ivar), "subIvar"));
ivar = class_getInstanceVariable([ShrinkingSuper class], "superIvar");
testassert(!ivar);
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *superlayout;
const uint8_t *sublayout;
superlayout = class_getIvarLayout([ShrinkingSuper class]);
sublayout = class_getIvarLayout([Sub2 class]);
// only `isa` is left; superIvar[] and superIvar2[] are gone
testassert(superlayout == NULL || 0 == ustrcmp(superlayout, "\x01"));
testassert(0 == ustrcmp(sublayout, "\x01\xb1"));
superlayout = class_getWeakIvarLayout([ShrinkingSuper class]);
sublayout = class_getWeakIvarLayout([Sub2 class]);
testassert(superlayout == NULL);
testassert(0 == ustrcmp(sublayout, "\xb1\x10"));
}
/*
Ivars slide but GC layouts stay the same
Here, the last word of the superclass is misaligned, but
its GC layout includes a bit for that whole word.
Additionally, all of the subclass ivars fit into that word too,
both before and after sliding.
The runtime will try to slide the GC layout and must not be
confused (rdar://6851700). Note that the second skip-word may or may
not actually be included, because it crosses the end of the object.
Compile-time layout of NoGCChangeSub:
[0 scan] isa
[1 skip] d
[2 skip] superc1, subc3
Runtime layout of NoGCChangeSub:
[0 scan] isa
[1 skip] d
[2 skip] superc1, superc2, subc3
*/
if (FIXME_CHECK_ARC_LAYOUTS) {
Ivar ivar1 = class_getInstanceVariable([NoGCChangeSub class], "superc1");
testassert(ivar1);
Ivar ivar2 = class_getInstanceVariable([NoGCChangeSub class], "superc2");
testassert(ivar2);
Ivar ivar3 = class_getInstanceVariable([NoGCChangeSub class], "subc3");
testassert(ivar3);
testassert(ivar_getOffset(ivar1) != ivar_getOffset(ivar2) &&
ivar_getOffset(ivar1) != ivar_getOffset(ivar3) &&
ivar_getOffset(ivar2) != ivar_getOffset(ivar3));
}
/* Ivar layout includes runs of 15 words.
rdar://6859875 this would generate a truncated GC layout.
*/
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *layout =
class_getIvarLayout(objc_getClass("RunsOf15Sub"));
testassert(layout);
int totalSkip = 0;
int totalScan = 0;
// should find 30+ each of skip and scan
uint8_t c;
while ((c = *layout++)) {
totalSkip += c>>4;
totalScan += c&0xf;
}
testassert(totalSkip >= 30);
testassert(totalScan >= 30);
}
/*
Non-strong -> strong
Classes do not change size, but GC layouts must be updated.
Both new and old ABI detect this case (rdar://5774578)
Compile-time layout of MoreStrongSub:
[0 scan] isa
[1 skip] superIvar
[2 scan] subIvar
Runtime layout of MoreStrongSub:
[0 scan] isa
[1 scan] superIvar
[2 scan] subIvar
*/
testassert(class_getInstanceSize([MoreStrongSub class]) == 3*sizeof(void*));
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *layout;
layout = class_getIvarLayout([MoreStrongSub class]);
testassert(layout == NULL);
layout = class_getWeakIvarLayout([MoreStrongSub class]);
testassert(layout == NULL);
}
/*
Strong -> weak
Classes do not change size, but GC layouts must be updated.
Old ABI intentionally does not detect this case (rdar://5774578)
Compile-time layout of MoreWeakSub:
[0 scan] isa
[1 scan] superIvar
[2 scan] subIvar
Runtime layout of MoreWeakSub:
[0 scan] isa
[1 weak] superIvar
[2 scan] subIvar
*/
testassert(class_getInstanceSize([MoreWeakSub class]) == 3*sizeof(void*));
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *layout;
layout = class_getIvarLayout([MoreWeakSub class]);
testassert(0 == ustrcmp(layout, "\x01\x11"));
layout = class_getWeakIvarLayout([MoreWeakSub class]);
testassert(0 == ustrcmp(layout, "\x11\x10"));
}
/*
Non-strong -> weak
Classes do not change size, but GC layouts must be updated.
Old ABI intentionally does not detect this case (rdar://5774578)
Compile-time layout of MoreWeak2Sub:
[0 scan] isa
[1 skip] superIvar
[2 scan] subIvar
Runtime layout of MoreWeak2Sub:
[0 scan] isa
[1 weak] superIvar
[2 scan] subIvar
*/
testassert(class_getInstanceSize([MoreWeak2Sub class]) == 3*sizeof(void*));
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *layout;
layout = class_getIvarLayout([MoreWeak2Sub class]);
testassert(0 == ustrcmp(layout, "\x01\x11") ||
0 == ustrcmp(layout, "\x01\x10\x01"));
layout = class_getWeakIvarLayout([MoreWeak2Sub class]);
testassert(0 == ustrcmp(layout, "\x11\x10"));
}
/*
Strong -> non-strong
Classes do not change size, but GC layouts must be updated.
Old ABI intentionally does not detect this case (rdar://5774578)
Compile-time layout of LessStrongSub:
[0 scan] isa
[1 scan] superIvar
[2 scan] subIvar
Runtime layout of LessStrongSub:
[0 scan] isa
[1 skip] superIvar
[2 scan] subIvar
*/
testassert(class_getInstanceSize([LessStrongSub class]) == 3*sizeof(void*));
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *layout;
layout = class_getIvarLayout([LessStrongSub class]);
testassert(0 == ustrcmp(layout, "\x01\x11"));
layout = class_getWeakIvarLayout([LessStrongSub class]);
testassert(layout == NULL);
}
/*
Weak -> strong
Classes do not change size, but GC layouts must be updated.
Both new and old ABI detect this case (rdar://5774578 rdar://6924114)
Compile-time layout of LessWeakSub:
[0 scan] isa
[1 weak] superIvar
[2 scan] subIvar
Runtime layout of LessWeakSub:
[0 scan] isa
[1 scan] superIvar
[2 scan] subIvar
*/
testassert(class_getInstanceSize([LessWeakSub class]) == 3*sizeof(void*));
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *layout;
layout = class_getIvarLayout([LessWeakSub class]);
testassert(layout == NULL);
layout = class_getWeakIvarLayout([LessWeakSub class]);
testassert(layout == NULL);
}
/*
Weak -> non-strong
Classes do not change size, but GC layouts must be updated.
Old ABI intentionally does not detect this case (rdar://5774578)
Compile-time layout of LessWeak2Sub:
[0 scan] isa
[1 weak] superIvar
[2 scan] subIvar
Runtime layout of LessWeak2Sub:
[0 scan] isa
[1 skip] superIvar
[2 scan] subIvar
*/
testassert(class_getInstanceSize([LessWeak2Sub class]) == 3*sizeof(void*));
if (FIXME_CHECK_ARC_LAYOUTS) {
const uint8_t *layout;
layout = class_getIvarLayout([LessWeak2Sub class]);
testassert(0 == ustrcmp(layout, "\x01\x11") ||
0 == ustrcmp(layout, "\x01\x10\x01"));
layout = class_getWeakIvarLayout([LessWeak2Sub class]);
testassert(layout == NULL);
}
succeed(basename(argv[0]));
return 0;
}