Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix NSArray description #478

Merged
merged 19 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,13 @@
Identifier = "KSDebug_Tests">
</Test>
<Test
Identifier = "KSObjC_Tests/testArrayDescription">
Identifier = "KSObjC_Tests/testDateDescription">
</Test>
<Test
Identifier = "KSObjC_Tests/testCopyArrayContentsMutable">
Identifier = "KSObjC_Tests/testDateIsValid">
</Test>
<Test
Identifier = "KSObjC_Tests/testGetDateContents">
</Test>
<Test
Identifier = "KSObjC_Tests/testDateDescription">
Expand Down
138 changes: 110 additions & 28 deletions Sources/KSCrashRecordingCore/KSObjC.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,23 @@ static int taggedStringDescription(const void* object, char* buffer, int bufferL

static ClassData g_classData[] =
{
{"__NSCFString", KSObjCClassTypeString, ClassSubtypeNone, true, stringIsValid, stringDescription},
{"NSCFString", KSObjCClassTypeString, ClassSubtypeNone, true, stringIsValid, stringDescription},
{"__NSCFConstantString", KSObjCClassTypeString, ClassSubtypeNone, true, stringIsValid, stringDescription},
{"NSCFConstantString", KSObjCClassTypeString, ClassSubtypeNone, true, stringIsValid, stringDescription},
{"__NSArray0", KSObjCClassTypeArray, ClassSubtypeNSArrayImmutable, false, arrayIsValid, arrayDescription},
{"__NSArrayI", KSObjCClassTypeArray, ClassSubtypeNSArrayImmutable, false, arrayIsValid, arrayDescription},
{"__NSArrayM", KSObjCClassTypeArray, ClassSubtypeNSArrayMutable, true, arrayIsValid, arrayDescription},
{"__NSCFArray", KSObjCClassTypeArray, ClassSubtypeCFArray, false, arrayIsValid, arrayDescription},
{"NSCFArray", KSObjCClassTypeArray, ClassSubtypeCFArray, false, arrayIsValid, arrayDescription},
{"__NSDate", KSObjCClassTypeDate, ClassSubtypeNone, false, dateIsValid, dateDescription},
{"NSDate", KSObjCClassTypeDate, ClassSubtypeNone, false, dateIsValid, dateDescription},
{"__NSCFNumber", KSObjCClassTypeNumber, ClassSubtypeNone, false, numberIsValid, numberDescription},
{"NSCFNumber", KSObjCClassTypeNumber, ClassSubtypeNone, false, numberIsValid, numberDescription},
{"NSNumber", KSObjCClassTypeNumber, ClassSubtypeNone, false, numberIsValid, numberDescription},
{"NSURL", KSObjCClassTypeURL, ClassSubtypeNone, false, urlIsValid, urlDescription},
{NULL, KSObjCClassTypeUnknown, ClassSubtypeNone, false, objectIsValid, objectDescription},
{"__NSCFString", KSObjCClassTypeString, ClassSubtypeNone, true, stringIsValid, stringDescription},
{"NSCFString", KSObjCClassTypeString, ClassSubtypeNone, true, stringIsValid, stringDescription},
{"__NSCFConstantString", KSObjCClassTypeString, ClassSubtypeNone, true, stringIsValid, stringDescription},
{"NSCFConstantString", KSObjCClassTypeString, ClassSubtypeNone, true, stringIsValid, stringDescription},
{"__NSArray0", KSObjCClassTypeArray, ClassSubtypeNSArrayImmutable, false, arrayIsValid, arrayDescription},
{"__NSArrayI", KSObjCClassTypeArray, ClassSubtypeNSArrayImmutable, false, arrayIsValid, arrayDescription},
{"__NSArrayM", KSObjCClassTypeArray, ClassSubtypeNSArrayMutable, true, arrayIsValid, arrayDescription},
{"__NSCFArray", KSObjCClassTypeArray, ClassSubtypeCFArray, false, arrayIsValid, arrayDescription},
{"__NSSingleObjectArrayI", KSObjCClassTypeArray, ClassSubtypeNSArrayImmutable, false, arrayIsValid, arrayDescription},
{"NSCFArray", KSObjCClassTypeArray, ClassSubtypeCFArray, false, arrayIsValid, arrayDescription},
{"__NSDate", KSObjCClassTypeDate, ClassSubtypeNone, false, dateIsValid, dateDescription},
{"NSDate", KSObjCClassTypeDate, ClassSubtypeNone, false, dateIsValid, dateDescription},
{"__NSCFNumber", KSObjCClassTypeNumber, ClassSubtypeNone, false, numberIsValid, numberDescription},
{"NSCFNumber", KSObjCClassTypeNumber, ClassSubtypeNone, false, numberIsValid, numberDescription},
{"NSNumber", KSObjCClassTypeNumber, ClassSubtypeNone, false, numberIsValid, numberDescription},
{"NSURL", KSObjCClassTypeURL, ClassSubtypeNone, false, urlIsValid, urlDescription},
{NULL, KSObjCClassTypeUnknown, ClassSubtypeNone, false, objectIsValid, objectDescription},
};

static ClassData g_taggedClassData[] =
Expand Down Expand Up @@ -1500,6 +1501,9 @@ static int taggedNumberDescription(const void* object, char* buffer, int bufferL
#pragma mark - NSArray -
//======================================================================

/**
* For old types
*/
struct NSArray
{
struct
Expand All @@ -1510,6 +1514,37 @@ struct NSArray
} basic;
};


/**
* @struct NSArrayDescriptor
* @brief Descriptor for new types like `__NSSingleObjectArrayI`, `__NSArrayM`, `__NSFrozenArrayM`.
*
* This structure is used to describe the internal representation of various mutable and single-object NSArray types.
* It is adapted from the LLVM `NSArrayM` descriptor to provide compatibility with different types of arrays,
* such as `__NSSingleObjectArrayI`, `__NSArrayM`, and `__NSFrozenArrayM`.
*
* @details This structure was inspired by the LLVM code found in the NSArray.cpp file:
* https://github.com/apple/llvm-project/blob/29180d27e709b76965cc02c338188e37f2df9e7f/lldb/source/Plugins/Language/ObjC/NSArray.cpp#L148-L156
* The first two fields, `_cow` (which often represents ISA) and `_data`, are also applicable for cases with
* `__NSSingleObjectArrayI`.
*
* Many older versions of Foundation have different layouts and logic for different array types. Therefore, it is
* crucial not to use these fields directly without inspecting Apple's code and making additional checks. This structure
* is used here because it fits the current needs, but if something else is required (such as implementing mutable array
* contents), it may require a different struct.
*
* @note The `packed` attribute ensures that there is no padding between the fields of the structure.
*/
typedef struct __attribute__((packed))
{
uintptr_t _cow;
uintptr_t _data;
uint32_t _offset;
uint32_t _size;
uint32_t _muts;
uint32_t _used;
} NSArrayDescriptor;

static inline bool nsarrayIsMutable(const void* const arrayPtr)
{
return getClassDataFromObject(arrayPtr)->isMutable;
Expand All @@ -1521,38 +1556,85 @@ static inline bool nsarrayIsValid(const void* const arrayPtr)
return ksmem_copySafely(arrayPtr, &temp, sizeof(temp.basic));
}

/**
* Get the count of elements in an NSArray.
*
* @note This function is based on the LLVM code in the NSArray.cpp file:
* https://github.com/apple/llvm-project/blob/29180d27e709b76965cc02c338188e37f2df9e7f/lldb/source/Plugins/Language/ObjC/NSArray.cpp#L396-L412
*/
static inline int nsarrayCount(const void* const arrayPtr)
{
const struct NSArray* array = arrayPtr;
return array->basic.count < 0 ? 0 : (int)array->basic.count;
const char* const className = ksobjc_objectClassName(arrayPtr);
bool isMutable = kCFCoreFoundationVersionNumber > 1437 && strcmp(className, "__NSArrayM") == 0;
bool isFrozen = kCFCoreFoundationVersionNumber > 1436 && strcmp(className, "__NSFrozenArrayM") == 0;

if (isMutable || isFrozen)
{
NSArrayDescriptor descriptor = { 0 };
if (ksmem_copySafely((const void*)((uintptr_t)arrayPtr + sizeof(uintptr_t)), &descriptor,
sizeof(NSArrayDescriptor)))
{
return descriptor._used;
}
}
else if (strcmp(className, "__NSSingleObjectArrayI") == 0)
{
return 1;
}
else if (strcmp(className, "__NSArray0") == 0)
{
return 0;
}
else
{
const struct NSArray* array = arrayPtr;
return (array->basic.count >= 0) ? (int)array->basic.count : 0;
}
return 0;
}

static int nsarrayContents(const void* const arrayPtr, uintptr_t* contents, int count)
{
const struct NSArray* array = arrayPtr;

if(array->basic.count < (CFIndex)count)
int actualCount = nsarrayCount(arrayPtr);
const char* const className = ksobjc_objectClassName(arrayPtr);

if (actualCount < count)
{
if(array->basic.count <= 0)
if (actualCount <= 0)
{
return 0;
}
count = (int)array->basic.count;
count = actualCount;
}
// TODO: implement this (requires bit-field unpacking) in ksobj_ivarValue
if(nsarrayIsMutable(arrayPtr))

if (nsarrayIsMutable(arrayPtr))
{
return 0;
}

if(!ksmem_copySafely(&array->basic.firstEntry, contents, (int)sizeof(*contents) * count))

const uintptr_t* entry = NULL;

if (strcmp(className, "__NSSingleObjectArrayI") == 0)
{
const NSArrayDescriptor* arrayI = (const NSArrayDescriptor*)arrayPtr;
// Using a temp variable to handle aligment of NSArrayDescriptor
uintptr_t temp_data = arrayI->_data;
entry = &temp_data;
}
else
{
const struct NSArray* array = (const struct NSArray*)arrayPtr;
entry = (const uintptr_t*)&array->basic.firstEntry;
}

if (!ksmem_copySafely(entry, contents, sizeof(*contents) * count))
{
return 0;
}

return count;
}


static inline bool cfarrayIsValid(const void* const arrayPtr)
{
struct __CFArray temp;
Expand Down
23 changes: 20 additions & 3 deletions Tests/KSCrashRecordingCoreTests/KSObjC_Tests.m
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ - (void) testArrayDescriptionEmpty

- (void) testArrayDescription
{
NSArray* array = [NSArray arrayWithObjects:@"test", nil];
NSArray* array = [NSArray arrayWithObjects:@"test", nil]; // __NSSingleObjectArrayI
void* arrayPtr = (__bridge void*)array;
NSString* expectedClassName = [NSString stringWithCString:class_getName([array class]) encoding:NSUTF8StringEncoding];
NSString* expectedTheRest = @"\"test\"";
Expand All @@ -761,7 +761,7 @@ - (void) testArrayDescription

- (void) testCopyArrayContentsImmutable
{
NSArray* array = [NSArray arrayWithObjects:@"1", @"2", @"3", @"4", nil];
NSArray* array = [NSArray arrayWithObjects:@"1", @"2", @"3", @"4", nil]; // __NSArrayI
void* arrayPtr = (__bridge void*)array;
int expectedCount = (int)array.count;
int count = ksobjc_arrayCount(arrayPtr);
Expand Down Expand Up @@ -792,7 +792,7 @@ - (void) testCopyArrayContentsImmutableEmpty

- (void) testCopyArrayContentsMutable
{
NSMutableArray* array = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", @"4", nil];
NSMutableArray* array = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", @"4", nil]; // __NSArrayM
void* arrayPtr = (__bridge void*)array;
int expectedCount = (int)array.count;
int count = ksobjc_arrayCount(arrayPtr);
Expand All @@ -803,6 +803,23 @@ - (void) testCopyArrayContentsMutable
XCTAssertEqual(copied, expectedCopied, @"");
}

- (void) testCopyArrayContentsCopyOfMutable
{
NSMutableArray *array = [NSMutableArray array];
int size = 100;
for (NSUInteger i = 0; i < size; i++) { [array addObject:@(i)]; }

NSArray *copy = array.copy; // __NSFrozenArrayM
void* arrayPtr = (__bridge void*)copy;
int expectedCount = (int)array.count;
int count = ksobjc_arrayCount(arrayPtr);
XCTAssertEqual(count, expectedCount, @"");
uintptr_t contents[size + 10];
int copied = ksobjc_arrayContents(arrayPtr, contents, (int)sizeof(contents));
int expectedCopied = size;
XCTAssertEqual(copied, expectedCopied, @"");
}

- (void) testCopyArrayContentsMutableEmpty
{
NSMutableArray* array = [NSMutableArray array];
Expand Down
Loading