Skip to content

Commit c9ffd8f

Browse files
committed
Fixed a memory leak when the JSON being parsed contained associative arrays where the same key appeared more than once. Also hacked around the CFNumber limitation where CFNumbers only have a signed type for numbers and do not have an explicity unsigned type. Switched the README to README.md.
1 parent abd480e commit c9ffd8f

4 files changed

Lines changed: 254 additions & 21 deletions

File tree

JSONKit.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ typedef struct {
106106

107107
typedef struct {
108108
void **objects, **keys;
109-
size_t count, index, roundSizeUpToMultipleOf;
109+
JKHash *hashes;
110+
size_t *sizes;
111+
size_t count, index, roundSizeUpToMultipleOf;
110112
JKObjectStackFlags flags;
111113
} JKObjectStack;
112114

@@ -172,6 +174,15 @@ enum {
172174
};
173175
typedef JKFlags JKParseOptionFlags;
174176

177+
typedef id (*NSNumberAllocImp)(id object, SEL selector);
178+
typedef id (*NSNumberInitWithUnsignedLongLongImp)(id object, SEL selector, unsigned long long value);
179+
180+
typedef struct {
181+
Class NSNumberClass;
182+
NSNumberAllocImp NSNumberAlloc;
183+
NSNumberInitWithUnsignedLongLongImp NSNumberInitWithUnsignedLongLong;
184+
} JKObjCImpCache;
185+
175186
typedef struct {
176187
JKParseOptionFlags parseOptionFlags;
177188
JKConstBuffer stringBuffer;
@@ -181,6 +192,7 @@ typedef struct {
181192
JKParseToken token;
182193
JKObjectStack objectStack;
183194
JKTokenCache cache;
195+
JKObjCImpCache objCImpCache;
184196
NSError *error;
185197
} JKParseState;
186198

JSONKit.m

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ The code in isValidCodePoint() is derived from the ICU code in
323323
static void jk_managedBuffer_setToStackBuffer(JKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length);
324324
static unsigned char *jk_managedBuffer_resize(JKManagedBuffer *managedBuffer, size_t newSize);
325325
static void jk_objectStack_release(JKObjectStack *objectStack);
326-
static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, size_t count);
326+
static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, JKHash *hashes, size_t *sizes, size_t count);
327327
static int jk_objectStack_resize(JKObjectStack *objectStack, size_t newCount);
328328

329329
static void jk_error(JKParseState *parseState, NSString *format, ...);
@@ -334,6 +334,7 @@ The code in isValidCodePoint() is derived from the ICU code in
334334
static int jk_parse_next_token(JKParseState *parseState);
335335
static void jk_set_parsed_token(JKParseState *parseState, const unsigned char *ptr, size_t length, JKTokenType type, size_t advanceBy);
336336
static void jk_error_parse_accept_or3(JKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String);
337+
static void *jk_create_dictionary(JKParseState *parseState, size_t startingObjectIndex);
337338
static void *jk_parse_dictionary(JKParseState *parseState);
338339
static void *jk_parse_array(JKParseState *parseState);
339340
static void *jk_object_for_token(JKParseState *parseState);
@@ -472,11 +473,13 @@ static void jk_objectStack_release(JKObjectStack *objectStack) {
472473
objectStack->flags &= ~JKObjectStackLocationMask;
473474
}
474475

475-
static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, size_t count) {
476-
NSCParameterAssert((objectStack != NULL) && (objects != NULL) && (keys != NULL) && (count > 0UL));
476+
static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, JKHash *hashes, size_t *sizes, size_t count) {
477+
NSCParameterAssert((objectStack != NULL) && (objects != NULL) && (keys != NULL) && (hashes != NULL) && (sizes != NULL) && (count > 0UL));
477478
jk_objectStack_release(objectStack);
478479
objectStack->objects = objects;
479480
objectStack->keys = keys;
481+
objectStack->hashes = hashes;
482+
objectStack->sizes = sizes;
480483
objectStack->count = count;
481484
objectStack->flags = (objectStack->flags & ~JKObjectStackLocationMask) | JKObjectStackOnStack;
482485
#ifndef NS_BLOCK_ASSERTIONS
@@ -487,28 +490,42 @@ static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **o
487490

488491
static int jk_objectStack_resize(JKObjectStack *objectStack, size_t newCount) {
489492
size_t roundedUpNewCount = newCount;
493+
int returnCode = 0;
494+
495+
void **newObjects = NULL, **newKeys = NULL;
496+
JKHash *newHashes = NULL;
497+
size_t *newSizes = NULL;
490498

491499
if(objectStack->roundSizeUpToMultipleOf > 0UL) { roundedUpNewCount = newCount + ((objectStack->roundSizeUpToMultipleOf - (newCount % objectStack->roundSizeUpToMultipleOf)) % objectStack->roundSizeUpToMultipleOf); }
492500

493501
if((roundedUpNewCount != objectStack->count) && (roundedUpNewCount > objectStack->count)) {
494502
if((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnStack) {
495503
NSCParameterAssert((objectStack->flags & JKObjectStackMustFree) == 0);
496-
void **newObjects = NULL, **newKeys = NULL;
497-
498-
if((newObjects = (void **)calloc(1, roundedUpNewCount * sizeof(void *))) == NULL) { return(1); }
504+
505+
if((newObjects = (void **)calloc(1, roundedUpNewCount * sizeof(void *))) == NULL) { returnCode = 1; goto errorExit; }
499506
memcpy(newObjects, objectStack->objects, jk_min(objectStack->count, roundedUpNewCount) * sizeof(void *));
500-
if((newKeys = (void **)calloc(1, roundedUpNewCount * sizeof(void *))) == NULL) { free(newObjects); return(1); }
507+
if((newKeys = (void **)calloc(1, roundedUpNewCount * sizeof(void *))) == NULL) { returnCode = 1; goto errorExit; }
501508
memcpy(newKeys, objectStack->keys, jk_min(objectStack->count, roundedUpNewCount) * sizeof(void *));
502509

510+
if((newHashes = (JKHash *)calloc(1, roundedUpNewCount * sizeof(JKHash))) == NULL) { returnCode = 1; goto errorExit; }
511+
memcpy(newHashes, objectStack->hashes, jk_min(objectStack->count, roundedUpNewCount) * sizeof(JKHash));
512+
if((newSizes = (size_t *)calloc(1, roundedUpNewCount * sizeof(size_t))) == NULL) { returnCode = 1; goto errorExit; }
513+
memcpy(newSizes, objectStack->sizes, jk_min(objectStack->count, roundedUpNewCount) * sizeof(size_t));
514+
503515
objectStack->flags = (objectStack->flags & ~JKObjectStackLocationMask) | (JKObjectStackOnHeap | JKObjectStackMustFree);
504-
objectStack->objects = newObjects;
505-
objectStack->keys = newKeys;
516+
objectStack->objects = newObjects; newObjects = NULL;
517+
objectStack->keys = newKeys; newKeys = NULL;
518+
objectStack->hashes = newHashes; newHashes = NULL;
519+
objectStack->sizes = newSizes; newSizes = NULL;
506520
objectStack->count = roundedUpNewCount;
507521
} else {
508522
NSCParameterAssert(((objectStack->flags & JKObjectStackMustFree) != 0) && ((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnHeap));
509-
void **newObjects = NULL, **newKeys = NULL;
510-
if((newObjects = (void **)realloc(objectStack->objects, roundedUpNewCount * sizeof(void *))) != NULL) { objectStack->objects = newObjects; } else { return(1); }
511-
if((newKeys = (void **)realloc(objectStack->keys, roundedUpNewCount * sizeof(void *))) != NULL) { objectStack->keys = newKeys; } else { return(1); }
523+
if((newObjects = (void **)realloc(objectStack->objects, roundedUpNewCount * sizeof(void *))) != NULL) { objectStack->objects = newObjects; newObjects = NULL; } else { returnCode = 1; goto errorExit; }
524+
if((newKeys = (void **)realloc(objectStack->keys, roundedUpNewCount * sizeof(void *))) != NULL) { objectStack->keys = newKeys; newKeys = NULL; } else { returnCode = 1; goto errorExit; }
525+
526+
if((newHashes = (JKHash *)realloc(objectStack->hashes, roundedUpNewCount * sizeof(JKHash))) != NULL) { objectStack->hashes = newHashes; newHashes = NULL; } else { returnCode = 1; goto errorExit; }
527+
if((newSizes = (size_t *)realloc(objectStack->sizes, roundedUpNewCount * sizeof(size_t))) != NULL) { objectStack->sizes = newSizes; newSizes = NULL; } else { returnCode = 1; goto errorExit; }
528+
512529
#ifndef NS_BLOCK_ASSERTIONS
513530
size_t idx;
514531
for(idx = objectStack->count; idx < roundedUpNewCount; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; }
@@ -517,7 +534,13 @@ static int jk_objectStack_resize(JKObjectStack *objectStack, size_t newCount) {
517534
}
518535
}
519536

520-
return(0);
537+
errorExit:
538+
if(newObjects != NULL) { free(newObjects); newObjects = NULL; }
539+
if(newKeys != NULL) { free(newKeys); newKeys = NULL; }
540+
if(newHashes != NULL) { free(newHashes); newHashes = NULL; }
541+
if(newSizes != NULL) { free(newSizes); newSizes = NULL; }
542+
543+
return(returnCode);
521544
}
522545

523546

@@ -896,6 +919,9 @@ static int jk_parse_number(JKParseState *parseState) {
896919
numberTempBuf[parseState->token.tokenPtrRange.length] = 0;
897920

898921
errno = 0;
922+
923+
// Treat "-0" as a floating point number, which is capable of representing negative zeros.
924+
if(isNegative && (parseState->token.tokenPtrRange.length == 2) && (numberTempBuf[1] == '0')) { isFloatingPoint = 1; }
899925

900926
if(isFloatingPoint) {
901927
parseState->token.value.number.doubleValue = strtod((const char *)numberTempBuf, (char **)&endOfNumber);
@@ -1070,7 +1096,10 @@ JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) {
10701096
switch(parseState->token.value.type) {
10711097
case JKValueTypeString: parsedAtom = (void *)CFStringCreateWithBytes(NULL, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length, kCFStringEncodingUTF8, 0); break;
10721098
case JKValueTypeLongLong: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.longLongValue); break;
1073-
case JKValueTypeUnsignedLongLong: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.unsignedLongLongValue); break;
1099+
case JKValueTypeUnsignedLongLong:
1100+
if(parseState->token.value.number.unsignedLongLongValue <= LLONG_MAX) { parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.unsignedLongLongValue); }
1101+
else { parsedAtom = (void *)parseState->objCImpCache.NSNumberInitWithUnsignedLongLong(parseState->objCImpCache.NSNumberAlloc(parseState->objCImpCache.NSNumberClass, @selector(alloc)), @selector(initWithUnsignedLongLong:), parseState->token.value.number.unsignedLongLongValue); }
1102+
break;
10741103
case JKValueTypeDouble: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberDoubleType, &parseState->token.value.number.doubleValue); break;
10751104
default: jk_error(parseState, @"Internal error: Unknown token value type. line #%ld", (long)__LINE__); break;
10761105
}
@@ -1162,6 +1191,38 @@ static void jk_error_parse_accept_or3(JKParseState *parseState, int state, NSStr
11621191
return(parsedArray);
11631192
}
11641193

1194+
static void * jk_create_dictionary(JKParseState *parseState, size_t startingObjectIndex) {
1195+
void *parsedDictionary = NULL;
1196+
1197+
parseState->objectStack.index--;
1198+
1199+
ssize_t numberOfDuplicateKeys = 0L, atIndex = 0L, cmpIndex = 0L;
1200+
for(atIndex = parseState->objectStack.index; atIndex >= (ssize_t)startingObjectIndex; atIndex--) {
1201+
for(cmpIndex = atIndex - 1L; cmpIndex >= (ssize_t)startingObjectIndex; cmpIndex--) {
1202+
if((parseState->objectStack.keys[atIndex] != NULL) && (parseState->objectStack.keys[cmpIndex] != NULL) && (parseState->objectStack.hashes[atIndex] == parseState->objectStack.hashes[cmpIndex]) && (parseState->objectStack.sizes[atIndex] == parseState->objectStack.sizes[cmpIndex]) && ((parseState->objectStack.keys[atIndex] == parseState->objectStack.keys[cmpIndex]) || CFEqual(parseState->objectStack.keys[atIndex], parseState->objectStack.keys[cmpIndex]))) {
1203+
numberOfDuplicateKeys++;
1204+
CFRelease(parseState->objectStack.keys[cmpIndex]); parseState->objectStack.keys[cmpIndex] = NULL;
1205+
CFRelease(parseState->objectStack.objects[cmpIndex]); parseState->objectStack.objects[cmpIndex] = NULL;
1206+
}
1207+
}
1208+
}
1209+
1210+
if(numberOfDuplicateKeys) {
1211+
atIndex = startingObjectIndex;
1212+
for(cmpIndex = startingObjectIndex; cmpIndex < (ssize_t)parseState->objectStack.index; cmpIndex++) {
1213+
if((parseState->objectStack.keys[cmpIndex] != NULL) && (cmpIndex != atIndex)) {
1214+
parseState->objectStack.keys[atIndex] = parseState->objectStack.keys[cmpIndex];
1215+
parseState->objectStack.objects[atIndex] = parseState->objectStack.objects[cmpIndex];
1216+
}
1217+
}
1218+
1219+
parseState->objectStack.index -= numberOfDuplicateKeys;
1220+
}
1221+
1222+
parsedDictionary = (void *)CFDictionaryCreate(NULL, (const void **)&parseState->objectStack.keys[startingObjectIndex], (const void **)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index) - startingObjectIndex, &jk_transferOwnershipDictionaryKeyCallBacks, &jk_transferOwnershipDictionaryValueCallBacks);
1223+
1224+
return(parsedDictionary);
1225+
}
11651226

11661227
static void *jk_parse_dictionary(JKParseState *parseState) {
11671228
size_t startingObjectIndex = parseState->objectStack.index;
@@ -1180,10 +1241,10 @@ static void jk_error_parse_accept_or3(JKParseState *parseState, int state, NSStr
11801241
switch(parseState->token.type) {
11811242
case JKTokenTypeString:
11821243
if(JK_EXPECTED((dictState & JKParseAcceptValue) == 0, 0U)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected string."); stopParsing = 1; break; }
1183-
if(JK_EXPECTED((key = jk_object_for_token(parseState)) == NULL, 0U)) { jk_error(parseState, @"Internal error: Key == NULL."); stopParsing = 1; break; } else { parseState->objectStack.keys[objectStackIndex] = key; }
1244+
if(JK_EXPECTED((key = jk_object_for_token(parseState)) == NULL, 0U)) { jk_error(parseState, @"Internal error: Key == NULL."); stopParsing = 1; break; } else { parseState->objectStack.keys[objectStackIndex] = key; parseState->objectStack.hashes[objectStackIndex] = parseState->token.value.hash; parseState->objectStack.sizes[objectStackIndex] = parseState->token.value.ptrRange.length;}
11841245
break;
11851246

1186-
case JKTokenTypeObjectEnd: if((JK_EXPECTED(dictState & JKParseAcceptEnd, 1U))) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedDictionary = (void *)CFDictionaryCreate(NULL, (const void **)&parseState->objectStack.keys[startingObjectIndex], (const void **)&parseState->objectStack.objects[startingObjectIndex], (--parseState->objectStack.index) - startingObjectIndex, &jk_transferOwnershipDictionaryKeyCallBacks, &jk_transferOwnershipDictionaryValueCallBacks); } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected '}'."); } stopParsing = 1; break;
1247+
case JKTokenTypeObjectEnd: if((JK_EXPECTED(dictState & JKParseAcceptEnd, 1U))) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedDictionary = jk_create_dictionary(parseState, startingObjectIndex); } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected '}'."); } stopParsing = 1; break;
11871248
case JKTokenTypeComma: if((JK_EXPECTED(dictState & JKParseAcceptComma, 1U))) { dictState = JKParseAcceptValue; parseState->objectStack.index--; continue; } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break;
11881249

11891250
default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, dictState, @"a \"STRING\"", @"a comma", @"a '}'"); stopParsing = 1; break;
@@ -1277,6 +1338,15 @@ - (id)initWithParseOptions:(JKParseOptionFlags)parseOptionFlags
12771338
parseState.token.tokenBuffer.roundSizeUpToMultipleOf = 4096UL;
12781339
parseState.objectStack.roundSizeUpToMultipleOf = 2048UL;
12791340

1341+
parseState.objCImpCache.NSNumberClass = [NSNumber class];
1342+
parseState.objCImpCache.NSNumberAlloc = (NSNumberAllocImp)[NSNumber methodForSelector:@selector(alloc)];
1343+
1344+
// Hacktacular. Need to do it this way due to the nature of class clusters.
1345+
id temp_NSNumber = [NSNumber alloc];
1346+
parseState.objCImpCache.NSNumberInitWithUnsignedLongLong = (NSNumberInitWithUnsignedLongLongImp)[temp_NSNumber methodForSelector:@selector(initWithUnsignedLongLong:)];
1347+
[[temp_NSNumber init] release];
1348+
temp_NSNumber = NULL;
1349+
12801350
parseState.cache.count = JK_CACHE_SLOTS;
12811351
if((parseState.cache.items = (JKTokenCacheItem *)calloc(1, sizeof(JKTokenCacheItem) * parseState.cache.count)) == NULL) { goto errorExit; }
12821352

@@ -1337,10 +1407,11 @@ - (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:
13371407
unsigned char stackTokenBuffer[JK_TOKENBUFFER_SIZE] JK_ALIGNED(64);
13381408
jk_managedBuffer_setToStackBuffer(&parseState.token.tokenBuffer, stackTokenBuffer, sizeof(stackTokenBuffer));
13391409

1340-
void *stackObjects[JK_STACK_OBJS] JK_ALIGNED(64);
1341-
void *stackKeys [JK_STACK_OBJS] JK_ALIGNED(64);
1342-
1343-
jk_objectStack_setToStackBuffer(&parseState.objectStack, stackObjects, stackKeys, JK_STACK_OBJS);
1410+
void *stackObjects[JK_STACK_OBJS] JK_ALIGNED(64);
1411+
void *stackKeys [JK_STACK_OBJS] JK_ALIGNED(64);
1412+
JKHash stackHashes [JK_STACK_OBJS] JK_ALIGNED(64);
1413+
size_t stackSizes [JK_STACK_OBJS] JK_ALIGNED(64);
1414+
jk_objectStack_setToStackBuffer(&parseState.objectStack, stackObjects, stackKeys, stackHashes, stackSizes, JK_STACK_OBJS);
13441415

13451416
id parsedJSON = json_parse_it(&parseState);
13461417

README

Whitespace-only changes.

0 commit comments

Comments
 (0)