From 2388b6fd71ed16ec0f485e2d0a14fd959424d4df Mon Sep 17 00:00:00 2001 From: John Engelhart Date: Sun, 18 Dec 2011 04:18:16 -0800 Subject: [PATCH 01/15] Updated README.md with benchmark information for NSJSONSerialization. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 56c5e2a..8b4a4d9 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Copyright © 2011, John Engelhart. ### A Very High Performance Objective-C JSON Library +**UPDATE:** (2011/12/18) The benchmarks below were performed before Apples [`NSJSONSerialization`][NSJSONSerialization] was available (as of Mac OS X 10.7 and iOS 5). The obvious question is: Which is faster, [`NSJSONSerialization`][NSJSONSerialization] or JSONKit? According to [this site](http://www.bonto.ch/blog/2011/12/08/json-libraries-for-ios-comparison-updated/), JSONKit is faster than [`NSJSONSerialization`][NSJSONSerialization]. Some quick "back of the envelope" calculations using the numbers reported, JSONKit appears to be approximately 25% to 40% faster than [`NSJSONSerialization`][NSJSONSerialization], which is pretty significant. + Parsing | Serializing :---------:|:-------------: Deserialize from JSON | Serialize to JSON @@ -305,3 +307,4 @@ Example | Result | Argument [strtoull]: http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/strtoull.3.html [getrusage]: http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man2/getrusage.2.html [printf]: http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/printf.3.html +[NSJSONSerialization]: http://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html From 02b983fa8f26521e47dc1d49e3a47ed062e20058 Mon Sep 17 00:00:00 2001 From: John Engelhart Date: Thu, 22 Mar 2012 10:32:17 -0700 Subject: [PATCH 02/15] Minor changes to hashing and cache aging algorithms. Hashing was changed slightly to exploit the fact that a significant amount of real world JSON strings / keys will have a high percentage of ASCII characters. Cache aging was modified to use an AIMD (additive increase, multiplicative decrease) policy. When an item is found in the cache, its age is incremented by one using saturating arithmetic. Ages are "quasi-randomly" aged using unsigned right shifts, or in other words its age is divided in half. Since ages decrease far more quickly than they increase, the cache can quickly adapt and converge on the "hot set". --- JSONKit.m | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/JSONKit.m b/JSONKit.m index 9f27f47..76a04f0 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -175,7 +175,7 @@ The code in isValidCodePoint() is derived from the ICU code in #define JK_CACHE_SLOTS (1UL << JK_CACHE_SLOTS_BITS) // JK_CACHE_PROBES is the number of probe attempts. #define JK_CACHE_PROBES (4UL) -// JK_INIT_CACHE_AGE must be (1 << AGE) - 1 +// JK_INIT_CACHE_AGE must be < (1 << AGE) - 1, where AGE is sizeof(typeof(AGE)) * 8. #define JK_INIT_CACHE_AGE (0) // JK_TOKENBUFFER_SIZE is the default stack size for the temporary buffer used to hold "non-simple" strings (i.e., contains \ escapes) @@ -609,7 +609,7 @@ - (void)releaseState; JK_STATIC_INLINE size_t jk_min(size_t a, size_t b); JK_STATIC_INLINE size_t jk_max(size_t a, size_t b); -JK_STATIC_INLINE JKHash calculateHash(JKHash currentHash, unsigned char c); +JK_STATIC_INLINE JKHash jk_calculateHash(JKHash currentHash, unsigned char c); // JSONKit v1.4 used both a JKArray : NSArray and JKMutableArray : NSMutableArray, and the same for the dictionary collection type. // However, Louis Gerbarg (via cocoa-dev) pointed out that Cocoa / Core Foundation actually implements only a single class that inherits from the @@ -1111,7 +1111,8 @@ - (id)mutableCopyWithZone:(NSZone *)zone JK_STATIC_INLINE size_t jk_min(size_t a, size_t b) { return((a < b) ? a : b); } JK_STATIC_INLINE size_t jk_max(size_t a, size_t b) { return((a > b) ? a : b); } -JK_STATIC_INLINE JKHash calculateHash(JKHash currentHash, unsigned char c) { return(((currentHash << 5) + currentHash) + c); } +JK_STATIC_INLINE JKHash jk_calculateHash(JKHash currentHash, unsigned char c) { return((((currentHash << 5) + currentHash) + (c - 29)) ^ (currentHash >> 19)); } + static void jk_error(JKParseState *parseState, NSString *format, ...) { NSCParameterAssert((parseState != NULL) && (format != NULL)); @@ -1408,7 +1409,7 @@ JK_STATIC_INLINE int jk_string_add_unicodeCodePoint(JKParseState *parseState, ui if((result = ConvertUTF32toUTF8(unicodeCodePoint, &u8s, (parseState->token.tokenBuffer.bytes.ptr + parseState->token.tokenBuffer.bytes.length))) != conversionOK) { if(result == targetExhausted) { return(1); } } size_t utf8len = u8s - &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx], nextIdx = (*tokenBufferIdx) + utf8len; - while(*tokenBufferIdx < nextIdx) { *stringHash = calculateHash(*stringHash, parseState->token.tokenBuffer.bytes.ptr[(*tokenBufferIdx)++]); } + while(*tokenBufferIdx < nextIdx) { *stringHash = jk_calculateHash(*stringHash, parseState->token.tokenBuffer.bytes.ptr[(*tokenBufferIdx)++]); } return(0); } @@ -1442,8 +1443,8 @@ static int jk_parse_string(JKParseState *parseState) { ConversionResult result; if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(atStringCharacter - 1, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { goto switchToSlowPath; } - stringHash = calculateHash(stringHash, currentChar); - while(atStringCharacter < nextValidCharacter) { NSCParameterAssert(JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)); stringHash = calculateHash(stringHash, *atStringCharacter++); } + stringHash = jk_calculateHash(stringHash, currentChar); + while(atStringCharacter < nextValidCharacter) { NSCParameterAssert(JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)); stringHash = jk_calculateHash(stringHash, *atStringCharacter++); } continue; } else { if(JK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = JSONStringStateFinished; goto finishedParsing; } @@ -1460,7 +1461,7 @@ static int jk_parse_string(JKParseState *parseState) { if(JK_EXPECT_F(currentChar < 0x20UL)) { jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing; } - stringHash = calculateHash(stringHash, currentChar); + stringHash = jk_calculateHash(stringHash, currentChar); } } @@ -1478,7 +1479,7 @@ static int jk_parse_string(JKParseState *parseState) { if(JK_EXPECT_T(currentChar < (unsigned long)0x80)) { // Not a UTF8 sequence if(JK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = JSONStringStateFinished; atStringCharacter++; goto finishedParsing; } if(JK_EXPECT_F(currentChar == (unsigned long)'\\')) { stringState = JSONStringStateEscape; continue; } - stringHash = calculateHash(stringHash, currentChar); + stringHash = jk_calculateHash(stringHash, currentChar); tokenBuffer[tokenBufferIdx++] = currentChar; continue; } else { // UTF8 sequence @@ -1493,7 +1494,7 @@ static int jk_parse_string(JKParseState *parseState) { atStringCharacter = nextValidCharacter - 1; continue; } else { - while(atStringCharacter < nextValidCharacter) { tokenBuffer[tokenBufferIdx++] = *atStringCharacter; stringHash = calculateHash(stringHash, *atStringCharacter++); } + while(atStringCharacter < nextValidCharacter) { tokenBuffer[tokenBufferIdx++] = *atStringCharacter; stringHash = jk_calculateHash(stringHash, *atStringCharacter++); } atStringCharacter--; continue; } @@ -1521,7 +1522,7 @@ static int jk_parse_string(JKParseState *parseState) { parsedEscapedChar: stringState = JSONStringStateParsing; - stringHash = calculateHash(stringHash, escapedChar); + stringHash = jk_calculateHash(stringHash, escapedChar); tokenBuffer[tokenBufferIdx++] = escapedChar; break; @@ -1709,7 +1710,7 @@ static int jk_parse_number(JKParseState *parseState) { if(JK_EXPECT_F(endOfNumber != &numberTempBuf[parseState->token.tokenPtrRange.length]) && JK_EXPECT_F(numberState != JSONNumberStateError)) { numberState = JSONNumberStateError; jk_error(parseState, @"The conversion function did not consume all of the number tokens characters."); } size_t hashIndex = 0UL; - for(hashIndex = 0UL; hashIndex < parseState->token.value.ptrRange.length; hashIndex++) { parseState->token.value.hash = calculateHash(parseState->token.value.hash, parseState->token.value.ptrRange.ptr[hashIndex]); } + for(hashIndex = 0UL; hashIndex < parseState->token.value.ptrRange.length; hashIndex++) { parseState->token.value.hash = jk_calculateHash(parseState->token.value.hash, parseState->token.value.ptrRange.ptr[hashIndex]); } } if(JK_EXPECT_F(numberState != JSONNumberStateFinished)) { jk_error(parseState, @"Invalid number."); } @@ -1972,7 +1973,7 @@ static id json_parse_it(JKParseState *parseState) { #pragma mark Object cache // This uses a Galois Linear Feedback Shift Register (LFSR) PRNG to pick which item in the cache to age. It has a period of (2^32)-1. -// NOTE: A LFSR *MUST* be initialized to a non-zero value and must always have a non-zero value. +// NOTE: A LFSR *MUST* be initialized to a non-zero value and must always have a non-zero value. The LFSR is initalized to 1 in -initWithParseOptions: JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) { NSCParameterAssert((parseState != NULL) && (parseState->cache.prng_lfsr != 0U)); parseState->cache.prng_lfsr = (parseState->cache.prng_lfsr >> 1) ^ ((0U - (parseState->cache.prng_lfsr & 1U)) & 0x80200003U); @@ -1983,9 +1984,8 @@ JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) { // // The hash table is a linear C array of JKTokenCacheItem. The terms "item" and "bucket" are synonymous with the index in to the cache array, i.e. cache.items[bucket]. // -// Items in the cache have an age associated with them. The age is the number of rightmost 1 bits, i.e. 0000 = 0, 0001 = 1, 0011 = 2, 0111 = 3, 1111 = 4. -// This allows us to use left and right shifts to add or subtract from an items age. Add = (age << 1) | 1. Subtract = age >> 0. Subtract is synonymous with "age" (i.e., age an item). -// The reason for this is it allows us to perform saturated adds and subtractions and is branchless. +// Items in the cache have an age associated with them. An items age is incremented using saturating unsigned arithmetic and decremeted using unsigned right shifts. +// Thus, an items age is managed using an AIMD policy- additive increase, multiplicative decrease. All age calculations and manipulations are branchless. // The primitive C type MUST be unsigned. It is currently a "char", which allows (at a minimum and in practice) 8 bits. // // A "useable bucket" is a bucket that is not in use (never populated), or has an age == 0. @@ -2000,12 +2000,12 @@ JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) { void *parsedAtom = NULL; if(JK_EXPECT_F(parseState->token.value.ptrRange.length == 0UL) && JK_EXPECT_T(parseState->token.value.type == JKValueTypeString)) { return(@""); } - + for(x = 0UL; x < JK_CACHE_PROBES; x++) { if(JK_EXPECT_F(parseState->cache.items[bucket].object == NULL)) { setBucket = 1UL; useableBucket = bucket; break; } if((JK_EXPECT_T(parseState->cache.items[bucket].hash == parseState->token.value.hash)) && (JK_EXPECT_T(parseState->cache.items[bucket].size == parseState->token.value.ptrRange.length)) && (JK_EXPECT_T(parseState->cache.items[bucket].type == parseState->token.value.type)) && (JK_EXPECT_T(parseState->cache.items[bucket].bytes != NULL)) && (JK_EXPECT_T(memcmp(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length) == 0U))) { - parseState->cache.age[bucket] = (parseState->cache.age[bucket] << 1) | 1U; + parseState->cache.age[bucket] = (((uint32_t)parseState->cache.age[bucket]) + 1U) - (((((uint32_t)parseState->cache.age[bucket]) + 1U) >> 31) ^ 1U); parseState->token.value.cacheItem = &parseState->cache.items[bucket]; NSCParameterAssert(parseState->cache.items[bucket].object != NULL); return((void *)CFRetain(parseState->cache.items[bucket].object)); From 5663f2db4664998c5235e1b5a02c70f745177ad3 Mon Sep 17 00:00:00 2001 From: John Engelhart Date: Sat, 23 Jun 2012 21:43:38 -0700 Subject: [PATCH 03/15] Silences some warnings that newer versions of the compiler(s) complain about. 1. Some of the string format argument specifiers used `%lu` to display `NSUInteger` values. `%lu` specifies a type of `unsigned long`, and `NSUInteger` maps to either `unsigned int` or `unsigned long`, depending on whether or not you're targeting a 32-bit or 64-bit ABI, respectively. On a 32-bit architecture (at least for the architectures / compilers that JSONKit will be used on), `unsigned long` and `unsigned int` just so happen to use the same primitive type- a 4 byte 32-bit value. On a 64-bit architecture (again, for the architectures / compilers that JSONKit will be used on), `unsigned int` stays the same at 4 bytes, however `unsigned long` grows to 64-bits / 8 bytes. The long and the short of it is this that using `%lu` allows you to easily support 32-bit and 64-bits with out a lot of effort. However, technically, the C standard says that `int` and `long` are strictly different ranks, and it's generally considered poor form to take advantage of platform specific details in this case. The proper fix is to add explicit type casts to `unsigned long`. This makes the implied behavior explicit, and eliminates the warnings from newer versions of the compiler. FYI, `gcc` did this for a long time and seems to be something that is either new or enabled by default in `clang` / `Xcode`. Other good warning flags you should consider enabling by default (from Xcode 4.3): Implicit Conversion to 32 Bit Type Suspicious Implicit Conversions Hidden Local Variables 2. Newer compiler warns about deprecated `id` `isa` usage. I found jwz's commentary about breaking API's pretty funny: http://www.jwz.org/blog/2012/06/i-have-ported-xscreensaver-to-the-iphone/ --- JSONKit.m | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/JSONKit.m b/JSONKit.m index 76a04f0..520b4be 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -742,14 +742,14 @@ - (NSUInteger)count - (void)getObjects:(id *)objectsPtr range:(NSRange)range { NSParameterAssert((objects != NULL) && (count <= capacity)); - if((objectsPtr == NULL) && (NSMaxRange(range) > 0UL)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: pointer to objects array is NULL but range length is %lu", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range)]; } - if((range.location > count) || (NSMaxRange(range) > count)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range), count]; } + if((objectsPtr == NULL) && (NSMaxRange(range) > 0UL)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: pointer to objects array is NULL but range length is %lu", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)NSMaxRange(range)]; } + if((range.location > count) || (NSMaxRange(range) > count)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)NSMaxRange(range), (unsigned long)count]; } memcpy(objectsPtr, objects + range.location, range.length * sizeof(id)); } - (id)objectAtIndex:(NSUInteger)objectIndex { - if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } + if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)objectIndex, (unsigned long)count]; } NSParameterAssert((objects != NULL) && (count <= capacity) && (objects[objectIndex] != NULL)); return(objects[objectIndex]); } @@ -770,7 +770,7 @@ - (void)insertObject:(id)anObject atIndex:(NSUInteger)objectIndex { if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(objectIndex > count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count + 1UL]; } + if(objectIndex > count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)objectIndex, (unsigned long)(count + 1UL)]; } #ifdef __clang_analyzer__ [anObject retain]; // Stupid clang analyzer... Issue #19. #else @@ -783,7 +783,7 @@ - (void)insertObject:(id)anObject atIndex:(NSUInteger)objectIndex - (void)removeObjectAtIndex:(NSUInteger)objectIndex { if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } + if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)objectIndex, (unsigned long)count]; } _JKArrayRemoveObjectAtIndex(self, objectIndex); mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; } @@ -792,7 +792,7 @@ - (void)replaceObjectAtIndex:(NSUInteger)objectIndex withObject:(id)anObject { if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } + if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)objectIndex, (unsigned long)count]; } #ifdef __clang_analyzer__ [anObject retain]; // Stupid clang analyzer... Issue #19. #else @@ -896,7 +896,7 @@ + (id)allocWithZone:(NSZone *)zone }; static NSUInteger _JKDictionaryCapacityForCount(NSUInteger count) { - NSUInteger bottom = 0UL, top = sizeof(jk_dictionaryCapacities) / sizeof(NSUInteger), mid = 0UL, tableSize = lround(floor((count) * 1.33)); + NSUInteger bottom = 0UL, top = sizeof(jk_dictionaryCapacities) / sizeof(NSUInteger), mid = 0UL, tableSize = (NSUInteger)lround(floor(((double)count) * 1.33)); while(top > bottom) { mid = (top + bottom) / 2UL; if(jk_dictionaryCapacities[mid] < tableSize) { bottom = mid + 1UL; } else { top = mid; } } return(jk_dictionaryCapacities[bottom]); } @@ -2592,21 +2592,23 @@ static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *object // // XXX XXX XXX XXX - BOOL workAroundMacOSXABIBreakingBug = NO; - if(JK_EXPECT_F(((NSUInteger)object) & 0x1)) { workAroundMacOSXABIBreakingBug = YES; goto slowClassLookup; } - if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.stringClass)) { isClass = JKClassString; } - else if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.numberClass)) { isClass = JKClassNumber; } - else if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.dictionaryClass)) { isClass = JKClassDictionary; } - else if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.arrayClass)) { isClass = JKClassArray; } - else if(JK_EXPECT_T(object->isa == encodeState->fastClassLookup.nullClass)) { isClass = JKClassNull; } + BOOL workAroundMacOSXABIBreakingBug = (JK_EXPECT_F(((NSUInteger)object) & 0x1)) ? YES : NO; + void *objectISA = (JK_EXPECT_F(workAroundMacOSXABIBreakingBug)) ? NULL : *((void **)objectPtr); + if(JK_EXPECT_F(workAroundMacOSXABIBreakingBug)) { goto slowClassLookup; } + + if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.stringClass)) { isClass = JKClassString; } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.numberClass)) { isClass = JKClassNumber; } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.dictionaryClass)) { isClass = JKClassDictionary; } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.arrayClass)) { isClass = JKClassArray; } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.nullClass)) { isClass = JKClassNull; } else { slowClassLookup: - if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.stringClass = object->isa; } isClass = JKClassString; } - else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.numberClass = object->isa; } isClass = JKClassNumber; } - else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.dictionaryClass = object->isa; } isClass = JKClassDictionary; } - else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.arrayClass = object->isa; } isClass = JKClassArray; } - else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.nullClass = object->isa; } isClass = JKClassNull; } + if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.stringClass = objectISA; } isClass = JKClassString; } + else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.numberClass = objectISA; } isClass = JKClassNumber; } + else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.dictionaryClass = objectISA; } isClass = JKClassDictionary; } + else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.arrayClass = objectISA; } isClass = JKClassArray; } + else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.nullClass = objectISA; } isClass = JKClassNull; } else { if((rerunningAfterClassFormatter == NO) && ( #ifdef __BLOCKS__ @@ -2788,7 +2790,8 @@ static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *object for(id keyObject in enumerateObject) { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; - if(JK_EXPECT_F((keyObject->isa != encodeState->fastClassLookup.stringClass)) && JK_EXPECT_F(([keyObject isKindOfClass:[NSString class]] == NO))) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } + void *keyObjectISA = *((void **)keyObject); + if(JK_EXPECT_F((keyObjectISA != encodeState->fastClassLookup.stringClass)) && JK_EXPECT_F(([keyObject isKindOfClass:[NSString class]] == NO))) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keyObject))) { return(1); } if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); } if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, (void *)CFDictionaryGetValue((CFDictionaryRef)object, keyObject)))) { return(1); } @@ -2799,7 +2802,8 @@ static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *object for(idx = 0L; idx < dictionaryCount; idx++) { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; - if(JK_EXPECT_F(((id)keys[idx])->isa != encodeState->fastClassLookup.stringClass) && JK_EXPECT_F([(id)keys[idx] isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } + void *keyObjectISA = *((void **)keys[idx]); + if(JK_EXPECT_F(keyObjectISA != encodeState->fastClassLookup.stringClass) && JK_EXPECT_F([(id)keys[idx] isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keys[idx]))) { return(1); } if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); } if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } From 82157634ca0ca5b6a4a67a194dd11f15d9b72835 Mon Sep 17 00:00:00 2001 From: John Engelhart Date: Thu, 12 Jul 2012 10:32:07 -0700 Subject: [PATCH 04/15] Add a `#ifdef` to silence a `clang` static analyzer false positive warning. The current (at the time of this writing) version of the `clang` static analyzer is complaing that the `objects` pointer is `NULL`. This is a false positive warning from the analyzer. --- JSONKit.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/JSONKit.m b/JSONKit.m index 520b4be..0e9331f 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -744,7 +744,9 @@ - (void)getObjects:(id *)objectsPtr range:(NSRange)range NSParameterAssert((objects != NULL) && (count <= capacity)); if((objectsPtr == NULL) && (NSMaxRange(range) > 0UL)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: pointer to objects array is NULL but range length is %lu", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)NSMaxRange(range)]; } if((range.location > count) || (NSMaxRange(range) > count)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)NSMaxRange(range), (unsigned long)count]; } +#ifndef __clang_analyzer__ memcpy(objectsPtr, objects + range.location, range.length * sizeof(id)); +#endif } - (id)objectAtIndex:(NSUInteger)objectIndex From e097f39e511ad197fbeb0d9ed245dda9d53e2fa1 Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Thu, 18 Oct 2012 18:22:04 +0200 Subject: [PATCH 05/15] Added JK_REMOVE_DEPRECATED macro to remove all deprecated stuff. --- JSONKit.h | 5 +++++ JSONKit.m | 2 ++ 2 files changed, 7 insertions(+) diff --git a/JSONKit.h b/JSONKit.h index 71bd0c3..865c1bc 100644 --- a/JSONKit.h +++ b/JSONKit.h @@ -95,6 +95,9 @@ typedef unsigned int NSUInteger; #ifndef _JSONKIT_H_ #define _JSONKIT_H_ +// You can define JK_REMOVE_DEPRECATED to remove all deprecated stuff +//#define JK_REMOVE_DEPRECATED + #if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465) #define JK_DEPRECATED_ATTRIBUTE __attribute__((deprecated)) #else @@ -148,12 +151,14 @@ typedef struct JKParseState JKParseState; // Opaque internal, private type. - (id)initWithParseOptions:(JKParseOptionFlags)parseOptionFlags; - (void)clearCache; +#ifndef JK_REMOVE_DEPRECATED // The parse... methods were deprecated in v1.4 in favor of the v1.4 objectWith... methods. - (id)parseUTF8String:(const unsigned char *)string length:(size_t)length JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length: instead. - (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length:error: instead. // The NSData MUST be UTF8 encoded JSON. - (id)parseJSONData:(NSData *)jsonData JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData: instead. - (id)parseJSONData:(NSData *)jsonData error:(NSError **)error JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData:error: instead. +#endif // Methods that return immutable collection objects. - (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length; diff --git a/JSONKit.m b/JSONKit.m index 0e9331f..f66359b 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -2203,6 +2203,7 @@ static id _JKParseUTF8String(JKParseState *parseState, BOOL mutableCollections, #pragma mark Deprecated as of v1.4 //////////// +#ifndef JK_REMOVE_DEPRECATED // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length: instead. - (id)parseUTF8String:(const unsigned char *)string length:(size_t)length { @@ -2226,6 +2227,7 @@ - (id)parseJSONData:(NSData *)jsonData error:(NSError **)error { return([self objectWithData:jsonData error:error]); } +#endif //////////// #pragma mark Methods that return immutable collection objects From f6c5407e69cbb1b1ab77a652abd63e2b4935a7d7 Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Sat, 23 Mar 2013 01:15:47 +0100 Subject: [PATCH 06/15] Fixed arrays and dictionaries creation. --- JSONKit.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/JSONKit.m b/JSONKit.m index f66359b..149d691 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -677,7 +677,7 @@ + (id)allocWithZone:(NSZone *)zone NSCParameterAssert((objects != NULL) && (_JKArrayClass != NULL) && (_JKArrayInstanceSize > 0UL)); JKArray *array = NULL; if(JK_EXPECT_T((array = (JKArray *)calloc(1UL, _JKArrayInstanceSize)) != NULL)) { // Directly allocate the JKArray instance via calloc. - array->isa = _JKArrayClass; + object_setClass(array, _JKArrayClass); if((array = [array init]) == NULL) { return(NULL); } array->capacity = count; array->count = count; @@ -928,7 +928,7 @@ static void _JKDictionaryResizeIfNeccessary(JKDictionary *dictionary) { NSCParameterAssert((keys != NULL) && (keyHashes != NULL) && (objects != NULL) && (_JKDictionaryClass != NULL) && (_JKDictionaryInstanceSize > 0UL)); JKDictionary *dictionary = NULL; if(JK_EXPECT_T((dictionary = (JKDictionary *)calloc(1UL, _JKDictionaryInstanceSize)) != NULL)) { // Directly allocate the JKDictionary instance via calloc. - dictionary->isa = _JKDictionaryClass; + object_setClass(dictionary, _JKDictionaryClass); if((dictionary = [dictionary init]) == NULL) { return(NULL); } dictionary->capacity = _JKDictionaryCapacityForCount(count); dictionary->count = 0UL; From e8ba3567b45fe536089c29ed5d9e1e7c293addb6 Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Sun, 26 May 2013 13:24:15 +0200 Subject: [PATCH 07/15] Ignore svn stuff. --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90ec22b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.svn From 07ea08ca1587ed93d42582d22abbc94ec410f365 Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Thu, 13 Nov 2014 12:10:55 +0100 Subject: [PATCH 08/15] Fixed tagged pointers once and for all. --- JSONKit.m | 237 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 164 insertions(+), 73 deletions(-) diff --git a/JSONKit.m b/JSONKit.m index 149d691..88ee3ee 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -222,6 +222,18 @@ The code in isValidCodePoint() is derived from the ICU code in #define JK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__)) #endif // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) +#if !(__OBJC2__ && __LP64__) +# define JK_SUPPORT_TAGGED_POINTERS 0 +#else +# define JK_SUPPORT_TAGGED_POINTERS 1 +#endif + +#if !JK_SUPPORT_TAGGED_POINTERS || !TARGET_OS_IPHONE +# define JK_SUPPORT_MSB_TAGGED_POINTERS 0 +#else +# define JK_SUPPORT_MSB_TAGGED_POINTERS 1 +#endif + @class JKArray, JKDictionaryEnumerator, JKDictionary; @@ -348,6 +360,9 @@ The code in isValidCodePoint() is derived from the ICU code in typedef struct JKRange JKRange; typedef struct JKManagedBuffer JKManagedBuffer; typedef struct JKFastClassLookup JKFastClassLookup; +#if JK_SUPPORT_TAGGED_POINTERS +typedef struct JKFastTagLookup JKFastTagLookup; +#endif typedef struct JKEncodeCache JKEncodeCache; typedef struct JKEncodeState JKEncodeState; typedef struct JKObjCImpCache JKObjCImpCache; @@ -459,6 +474,16 @@ The code in isValidCodePoint() is derived from the ICU code in void *nullClass; }; +#if JK_SUPPORT_TAGGED_POINTERS +struct JKFastTagLookup { + uintptr_t stringClass; + uintptr_t numberClass; + uintptr_t arrayClass; + uintptr_t dictionaryClass; + uintptr_t nullClass; +}; +#endif + struct JKEncodeCache { id object; size_t offset; @@ -470,6 +495,9 @@ The code in isValidCodePoint() is derived from the ICU code in JKManagedBuffer stringBuffer; size_t atIndex; JKFastClassLookup fastClassLookup; +#if JK_SUPPORT_TAGGED_POINTERS + JKFastTagLookup fastTagLookup; +#endif JKEncodeCache cache[JK_ENCODE_CACHE_SLOTS]; JKSerializeOptionFlags serializeOptionFlags; JKEncodeOptionType encodeOption; @@ -600,9 +628,9 @@ - (void)releaseState; static int jk_encode_write1slow(JKEncodeState *encodeState, ssize_t depthChange, const char *format); static int jk_encode_write1fast(JKEncodeState *encodeState, ssize_t depthChange JK_UNUSED_ARG, const char *format); static int jk_encode_writen(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length); -JK_STATIC_INLINE JKHash jk_encode_object_hash(void *objectPtr); +JK_STATIC_INLINE JKHash jk_encode_object_hash(const void *objectPtr); JK_STATIC_INLINE void jk_encode_updateCache(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object); -static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *objectPtr); +static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, const void *objectPtr); #define jk_encode_write1(es, dc, f) (JK_EXPECT_F(_jk_encode_prettyPrint) ? jk_encode_write1slow(es, dc, f) : jk_encode_write1fast(es, dc, f)) @@ -2525,11 +2553,124 @@ static int jk_encode_writen(JKEncodeState *encodeState, JKEncodeCache *cacheSlot return(0); } -JK_STATIC_INLINE JKHash jk_encode_object_hash(void *objectPtr) { +JK_STATIC_INLINE JKHash jk_encode_object_hash(const void *objectPtr) { return( ( (((JKHash)objectPtr) >> 21) ^ (((JKHash)objectPtr) >> 9) ) + (((JKHash)objectPtr) >> 4) ); } -static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *objectPtr) { + +// XXX XXX XXX XXX +// +// We need to work around a bug in 10.7, which breaks ABI compatibility with Objective-C going back not just to 10.0, but OpenStep and even NextStep. +// +// It has long been documented that "the very first thing that a pointer to an Objective-C object "points to" is a pointer to that objects class". +// +// This is euphemistically called "tagged pointers". There are a number of highly technical problems with this, most involving long passages from +// the C standard(s). In short, one can make a strong case, couched from the perspective of the C standard(s), that that 10.7 "tagged pointers" are +// fundamentally Wrong and Broken, and should have never been implemented. Assuming those points are glossed over, because the change is very clearly +// breaking ABI compatibility, this should have resulted in a minimum of a "minimum version required" bump in various shared libraries to prevent +// causes code that used to work just fine to suddenly break without warning. +// +// In fact, the C standard says that the hack below is "undefined behavior"- there is no requirement that the 10.7 tagged pointer hack of setting the +// "lower, unused bits" must be preserved when casting the result to an integer type, but this "works" because for most architectures +// `sizeof(long) == sizeof(void *)` and the compiler uses the same representation for both. (note: this is informal, not meant to be +// normative or pedantically correct). +// +// In other words, while this "works" for now, technically the compiler is not obligated to do "what we want", and a later version of the compiler +// is not required in any way to produce the same results or behavior that earlier versions of the compiler did for the statement below. +// +// Fan-fucking-tastic. +// +// Why not just use `object_getClass()`? Because `object->isa` reduces to (typically) a *single* instruction. Calling `object_getClass()` requires +// that the compiler potentially spill registers, establish a function call frame / environment, and finally execute a "jump subroutine" instruction. +// Then, the called subroutine must spend half a dozen instructions in its prolog, however many instructions doing whatever it does, then half a dozen +// instructions in its prolog. One instruction compared to dozens, maybe a hundred instructions. +// +// Yes, that's one to two orders of magnitude difference. Which is compelling in its own right. When going for performance, you're often happy with +// gains in the two to three percent range. +// +// XXX XXX XXX XXX + +#if JK_SUPPORT_TAGGED_POINTERS +JK_STATIC_INLINE BOOL jk_is_tagged_pointer(const void *objectPtr) +{ +#if JK_SUPPORT_MSB_TAGGED_POINTERS + return(((intptr_t)objectPtr) < 0); +#else + return(((uintptr_t)object) & 0x1); +#endif +} + +JK_STATIC_INLINE uintptr_t jk_get_tagged_pointer_tag(const void *objectPtr) +{ +#if JK_SUPPORT_MSB_TAGGED_POINTERS + return(((uintptr_t)objectPtr) >> 60); +#else + return(((uintptr_t)objectPtr) & 0x0F); +#endif +} +#endif + +JK_STATIC_INLINE int jk_object_class(JKEncodeState *encodeState, id object) { +#if JK_SUPPORT_TAGGED_POINTERS + if(jk_is_tagged_pointer(object)) { + uintptr_t objectTag = jk_get_tagged_pointer_tag(object); + + if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(JKClassString); } + else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.numberClass)) { return(JKClassNumber); } + else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.dictionaryClass)) { return(JKClassDictionary); } + else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.arrayClass)) { return(JKClassArray); } + else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.nullClass)) { return(JKClassNull); } + else { + if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastTagLookup.stringClass = objectTag; return(JKClassString); } + else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { encodeState->fastTagLookup.numberClass = objectTag; return(JKClassNumber); } + else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { encodeState->fastTagLookup.dictionaryClass = objectTag; return(JKClassDictionary); } + else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { encodeState->fastTagLookup.arrayClass = objectTag; return(JKClassArray); } + else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { encodeState->fastTagLookup.nullClass = objectTag; return(JKClassNull); } + } + } + else { +#endif + void *objectISA = *((void **)object); + + if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.stringClass)) { return(JKClassString); } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.numberClass)) { return(JKClassNumber); } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.dictionaryClass)) { return(JKClassDictionary); } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.arrayClass)) { return(JKClassArray); } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.nullClass)) { return(JKClassNull); } + else { + if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastClassLookup.stringClass = objectISA; return(JKClassString); } + else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { encodeState->fastClassLookup.numberClass = objectISA; return(JKClassNumber); } + else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { encodeState->fastClassLookup.dictionaryClass = objectISA; return(JKClassDictionary); } + else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { encodeState->fastClassLookup.arrayClass = objectISA; return(JKClassArray); } + else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { encodeState->fastClassLookup.nullClass = objectISA; return(JKClassNull); } + } +#if JK_SUPPORT_TAGGED_POINTERS + } +#endif + return(JKClassUnknown); +} + +JK_STATIC_INLINE BOOL jk_object_is_string(JKEncodeState *encodeState, id object) { +#if JK_SUPPORT_TAGGED_POINTERS + if(jk_is_tagged_pointer(object)) { + uintptr_t objectTag = jk_get_tagged_pointer_tag(object); + + if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(YES); } + else if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastTagLookup.stringClass = objectTag; return(YES); } + } + else { +#endif + void *objectISA = *((void **)object); + + if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.stringClass)) { return(YES); } + else if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastClassLookup.stringClass = objectISA; return(YES); } +#if JK_SUPPORT_TAGGED_POINTERS + } +#endif + return(NO); +} + +static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, const void *objectPtr) { NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (objectPtr != NULL)); id object = (id)objectPtr, encodeCacheObject = object; @@ -2562,67 +2703,18 @@ static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *object // we "re-run" the object check. However, we re-run the object check exactly ONCE. If the user supplies an object that isn't one of the // supported classes, we fail the second time (i.e., double fault error). BOOL rerunningAfterClassFormatter = NO; - rerunAfterClassFormatter:; - - // XXX XXX XXX XXX - // - // We need to work around a bug in 10.7, which breaks ABI compatibility with Objective-C going back not just to 10.0, but OpenStep and even NextStep. - // - // It has long been documented that "the very first thing that a pointer to an Objective-C object "points to" is a pointer to that objects class". - // - // This is euphemistically called "tagged pointers". There are a number of highly technical problems with this, most involving long passages from - // the C standard(s). In short, one can make a strong case, couched from the perspective of the C standard(s), that that 10.7 "tagged pointers" are - // fundamentally Wrong and Broken, and should have never been implemented. Assuming those points are glossed over, because the change is very clearly - // breaking ABI compatibility, this should have resulted in a minimum of a "minimum version required" bump in various shared libraries to prevent - // causes code that used to work just fine to suddenly break without warning. - // - // In fact, the C standard says that the hack below is "undefined behavior"- there is no requirement that the 10.7 tagged pointer hack of setting the - // "lower, unused bits" must be preserved when casting the result to an integer type, but this "works" because for most architectures - // `sizeof(long) == sizeof(void *)` and the compiler uses the same representation for both. (note: this is informal, not meant to be - // normative or pedantically correct). - // - // In other words, while this "works" for now, technically the compiler is not obligated to do "what we want", and a later version of the compiler - // is not required in any way to produce the same results or behavior that earlier versions of the compiler did for the statement below. - // - // Fan-fucking-tastic. - // - // Why not just use `object_getClass()`? Because `object->isa` reduces to (typically) a *single* instruction. Calling `object_getClass()` requires - // that the compiler potentially spill registers, establish a function call frame / environment, and finally execute a "jump subroutine" instruction. - // Then, the called subroutine must spend half a dozen instructions in its prolog, however many instructions doing whatever it does, then half a dozen - // instructions in its prolog. One instruction compared to dozens, maybe a hundred instructions. - // - // Yes, that's one to two orders of magnitude difference. Which is compelling in its own right. When going for performance, you're often happy with - // gains in the two to three percent range. - // - // XXX XXX XXX XXX - - - BOOL workAroundMacOSXABIBreakingBug = (JK_EXPECT_F(((NSUInteger)object) & 0x1)) ? YES : NO; - void *objectISA = (JK_EXPECT_F(workAroundMacOSXABIBreakingBug)) ? NULL : *((void **)objectPtr); - if(JK_EXPECT_F(workAroundMacOSXABIBreakingBug)) { goto slowClassLookup; } - - if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.stringClass)) { isClass = JKClassString; } - else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.numberClass)) { isClass = JKClassNumber; } - else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.dictionaryClass)) { isClass = JKClassDictionary; } - else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.arrayClass)) { isClass = JKClassArray; } - else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.nullClass)) { isClass = JKClassNull; } - else { - slowClassLookup: - if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.stringClass = objectISA; } isClass = JKClassString; } - else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.numberClass = objectISA; } isClass = JKClassNumber; } - else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.dictionaryClass = objectISA; } isClass = JKClassDictionary; } - else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.arrayClass = objectISA; } isClass = JKClassArray; } - else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.nullClass = objectISA; } isClass = JKClassNull; } - else { - if((rerunningAfterClassFormatter == NO) && ( + rerunAfterClassFormatter: + + isClass = jk_object_class(encodeState, object); + if(JK_EXPECT_F(isClass == JKClassUnknown)) { + if((rerunningAfterClassFormatter == NO) && ( #ifdef __BLOCKS__ - ((encodeState->classFormatterBlock) && ((object = encodeState->classFormatterBlock(object)) != NULL)) || + ((encodeState->classFormatterBlock) && ((object = encodeState->classFormatterBlock(object)) != nil)) || #endif - ((encodeState->classFormatterIMP) && ((object = encodeState->classFormatterIMP(encodeState->classFormatterDelegate, encodeState->classFormatterSelector, object)) != NULL)) )) { rerunningAfterClassFormatter = YES; goto rerunAfterClassFormatter; } + ((encodeState->classFormatterIMP) && ((object = encodeState->classFormatterIMP(encodeState->classFormatterDelegate, encodeState->classFormatterSelector, object)) != nil)) )) { rerunningAfterClassFormatter = YES; goto rerunAfterClassFormatter; } - if(rerunningAfterClassFormatter == NO) { jk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([encodeCacheObject class])); return(1); } - else { jk_encode_error(encodeState, @"Unable to serialize object class %@ that was returned by the unsupported class formatter. Original object class was %@.", (object == NULL) ? @"NULL" : NSStringFromClass([object class]), NSStringFromClass([encodeCacheObject class])); return(1); } - } + if(rerunningAfterClassFormatter == NO) { jk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([encodeCacheObject class])); return(1); } + else { jk_encode_error(encodeState, @"Unable to serialize object class %@ that was returned by the unsupported class formatter. Original object class was %@.", (object == nil) ? @"NULL" : NSStringFromClass([object class]), NSStringFromClass([encodeCacheObject class])); return(1); } } // This is here for the benefit of the optimizer. It allows the optimizer to do loop invariant code motion for the JKClassArray @@ -2790,15 +2882,14 @@ static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *object id enumerateObject = JK_EXPECT_F(_jk_encode_prettyPrint) ? [[object allKeys] sortedArrayUsingSelector:@selector(compare:)] : object; if(JK_EXPECT_F(jk_encode_write1(encodeState, 1L, "{"))) { return(1); } - if(JK_EXPECT_F(_jk_encode_prettyPrint) || JK_EXPECT_F(dictionaryCount > 1020L)) { + if(JK_EXPECT_F(_jk_encode_prettyPrint) || JK_EXPECT_F(dictionaryCount > 1024L)) { for(id keyObject in enumerateObject) { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; - void *keyObjectISA = *((void **)keyObject); - if(JK_EXPECT_F((keyObjectISA != encodeState->fastClassLookup.stringClass)) && JK_EXPECT_F(([keyObject isKindOfClass:[NSString class]] == NO))) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } - if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keyObject))) { return(1); } - if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); } - if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, (void *)CFDictionaryGetValue((CFDictionaryRef)object, keyObject)))) { return(1); } + if(JK_EXPECT_F(jk_object_is_string(encodeState, keyObject) == NO)) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } + if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keyObject))) { return(1); } + if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); } + if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, (void *)CFDictionaryGetValue((CFDictionaryRef)object, keyObject)))) { return(1); } } } else { void *keys[1024], *objects[1024]; @@ -2806,11 +2897,11 @@ static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *object for(idx = 0L; idx < dictionaryCount; idx++) { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; - void *keyObjectISA = *((void **)keys[idx]); - if(JK_EXPECT_F(keyObjectISA != encodeState->fastClassLookup.stringClass) && JK_EXPECT_F([(id)keys[idx] isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } - if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keys[idx]))) { return(1); } - if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); } - if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } + id keyObject = keys[idx]; + if(JK_EXPECT_F(jk_object_is_string(encodeState, keyObject) == NO)) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); } + if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keyObject))) { return(1); } + if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":"))) { return(1); } + if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } } } return(jk_encode_write1(encodeState, -1L, "}")); From 486608652bb5e29539a28a9d149166583a8db636 Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Mon, 17 Nov 2014 17:02:33 +0100 Subject: [PATCH 09/15] Fixed jk_get_tagged_pointer_tag() for LSB tagged pointers. --- JSONKit.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSONKit.m b/JSONKit.m index 88ee3ee..6f5b8ce 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -2596,7 +2596,7 @@ JK_STATIC_INLINE BOOL jk_is_tagged_pointer(const void *objectPtr) #if JK_SUPPORT_MSB_TAGGED_POINTERS return(((intptr_t)objectPtr) < 0); #else - return(((uintptr_t)object) & 0x1); + return(((uintptr_t)objectPtr) & 0x1); #endif } From bcaf007640112f5ce1db6c88ce85becde210634d Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Wed, 16 Sep 2015 23:23:48 +0200 Subject: [PATCH 10/15] Fixed compilation warning. --- JSONKit.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSONKit.m b/JSONKit.m index 6f5b8ce..fb9fe39 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -2879,7 +2879,7 @@ static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, const void * { int printComma = 0; CFIndex dictionaryCount = CFDictionaryGetCount((CFDictionaryRef)object), idx = 0L; - id enumerateObject = JK_EXPECT_F(_jk_encode_prettyPrint) ? [[object allKeys] sortedArrayUsingSelector:@selector(compare:)] : object; + id enumerateObject = JK_EXPECT_F(_jk_encode_prettyPrint) ? [[(NSDictionary*)object allKeys] sortedArrayUsingSelector:@selector(compare:)] : object; if(JK_EXPECT_F(jk_encode_write1(encodeState, 1L, "{"))) { return(1); } if(JK_EXPECT_F(_jk_encode_prettyPrint) || JK_EXPECT_F(dictionaryCount > 1024L)) { From 645617dd9cc3c19827574555f31df9787af63ed0 Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Sun, 22 Nov 2020 18:48:47 +0100 Subject: [PATCH 11/15] Added support for fast class lookup. --- JSONKit.m | 62 +++++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/JSONKit.m b/JSONKit.m index fb9fe39..f733a7d 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -234,6 +234,18 @@ The code in isValidCodePoint() is derived from the ICU code in # define JK_SUPPORT_MSB_TAGGED_POINTERS 1 #endif +#define JK_STATIC_CLASS_REF(CLASSNAME) ((Class)&OBJC_CLASS_$_ ## CLASSNAME) +#if __OBJC2__ +#define JK_DECLARE_STATIC_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME +#else +#define JK_DECLARE_STATIC_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME __asm(".objc_class_name_" # CLASSNAME) +#endif + +JK_DECLARE_STATIC_CLASS_REF(NSString); +JK_DECLARE_STATIC_CLASS_REF(NSNumber); +JK_DECLARE_STATIC_CLASS_REF(NSDictionary); +JK_DECLARE_STATIC_CLASS_REF(NSArray); +JK_DECLARE_STATIC_CLASS_REF(NSNull); @class JKArray, JKDictionaryEnumerator, JKDictionary; @@ -359,7 +371,6 @@ The code in isValidCodePoint() is derived from the ICU code in typedef struct JKConstPtrRange JKConstPtrRange; typedef struct JKRange JKRange; typedef struct JKManagedBuffer JKManagedBuffer; -typedef struct JKFastClassLookup JKFastClassLookup; #if JK_SUPPORT_TAGGED_POINTERS typedef struct JKFastTagLookup JKFastTagLookup; #endif @@ -466,14 +477,6 @@ The code in isValidCodePoint() is derived from the ICU code in BOOL mutableCollections; }; -struct JKFastClassLookup { - void *stringClass; - void *numberClass; - void *arrayClass; - void *dictionaryClass; - void *nullClass; -}; - #if JK_SUPPORT_TAGGED_POINTERS struct JKFastTagLookup { uintptr_t stringClass; @@ -494,7 +497,6 @@ The code in isValidCodePoint() is derived from the ICU code in JKManagedBuffer utf8ConversionBuffer; JKManagedBuffer stringBuffer; size_t atIndex; - JKFastClassLookup fastClassLookup; #if JK_SUPPORT_TAGGED_POINTERS JKFastTagLookup fastTagLookup; #endif @@ -2621,29 +2623,20 @@ JK_STATIC_INLINE int jk_object_class(JKEncodeState *encodeState, id object) { else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.arrayClass)) { return(JKClassArray); } else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.nullClass)) { return(JKClassNull); } else { - if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastTagLookup.stringClass = objectTag; return(JKClassString); } - else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { encodeState->fastTagLookup.numberClass = objectTag; return(JKClassNumber); } - else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { encodeState->fastTagLookup.dictionaryClass = objectTag; return(JKClassDictionary); } - else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { encodeState->fastTagLookup.arrayClass = objectTag; return(JKClassArray); } - else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { encodeState->fastTagLookup.nullClass = objectTag; return(JKClassNull); } + if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { encodeState->fastTagLookup.stringClass = objectTag; return(JKClassString); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSNumber)])) { encodeState->fastTagLookup.numberClass = objectTag; return(JKClassNumber); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSDictionary)])) { encodeState->fastTagLookup.dictionaryClass = objectTag; return(JKClassDictionary); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSArray)])) { encodeState->fastTagLookup.arrayClass = objectTag; return(JKClassArray); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSNull)])) { encodeState->fastTagLookup.nullClass = objectTag; return(JKClassNull); } } } else { #endif - void *objectISA = *((void **)object); - - if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.stringClass)) { return(JKClassString); } - else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.numberClass)) { return(JKClassNumber); } - else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.dictionaryClass)) { return(JKClassDictionary); } - else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.arrayClass)) { return(JKClassArray); } - else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.nullClass)) { return(JKClassNull); } - else { - if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastClassLookup.stringClass = objectISA; return(JKClassString); } - else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { encodeState->fastClassLookup.numberClass = objectISA; return(JKClassNumber); } - else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { encodeState->fastClassLookup.dictionaryClass = objectISA; return(JKClassDictionary); } - else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { encodeState->fastClassLookup.arrayClass = objectISA; return(JKClassArray); } - else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { encodeState->fastClassLookup.nullClass = objectISA; return(JKClassNull); } - } + if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { return(JKClassString); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSNumber)])) { return(JKClassNumber); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSDictionary)])) { return(JKClassDictionary); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSArray)])) { return(JKClassArray); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSNull)])) { return(JKClassNull); } #if JK_SUPPORT_TAGGED_POINTERS } #endif @@ -2656,14 +2649,11 @@ JK_STATIC_INLINE BOOL jk_object_is_string(JKEncodeState *encodeState, id object) uintptr_t objectTag = jk_get_tagged_pointer_tag(object); if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(YES); } - else if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastTagLookup.stringClass = objectTag; return(YES); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { encodeState->fastTagLookup.stringClass = objectTag; return(YES); } } else { #endif - void *objectISA = *((void **)object); - - if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.stringClass)) { return(YES); } - else if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastClassLookup.stringClass = objectISA; return(YES); } + if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { return(YES); } #if JK_SUPPORT_TAGGED_POINTERS } #endif @@ -2963,8 +2953,8 @@ - (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags enc unsigned char stackUTF8Buffer[JK_UTF8BUFFER_SIZE] JK_ALIGNED(64); jk_managedBuffer_setToStackBuffer(&encodeState->utf8ConversionBuffer, stackUTF8Buffer, sizeof(stackUTF8Buffer)); - if(((encodeOption & JKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:[NSArray class]] == NO) && ([object isKindOfClass:[NSDictionary class]] == NO))) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; } - if(((encodeOption & JKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; } + if(((encodeOption & JKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:JK_STATIC_CLASS_REF(NSArray)] == NO) && ([object isKindOfClass:JK_STATIC_CLASS_REF(NSDictionary)] == NO))) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; } + if(((encodeOption & JKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)] == NO)) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; } if(jk_encode_add_atom_to_buffer(encodeState, object) == 0) { BOOL stackBuffer = ((encodeState->stringBuffer.flags & JKManagedBufferMustFree) == 0UL) ? YES : NO; From 7f2d333c92e7b0496d3ec972b8627df2f02d4b38 Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Sun, 22 Nov 2020 18:49:23 +0100 Subject: [PATCH 12/15] Minor cleanup. --- JSONKit.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/JSONKit.m b/JSONKit.m index f733a7d..967cfb5 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -234,11 +234,11 @@ The code in isValidCodePoint() is derived from the ICU code in # define JK_SUPPORT_MSB_TAGGED_POINTERS 1 #endif -#define JK_STATIC_CLASS_REF(CLASSNAME) ((Class)&OBJC_CLASS_$_ ## CLASSNAME) +#define JK_STATIC_CLASS_REF(CLASSNAME) ((Class)&OBJC_CLASS_$_ ## CLASSNAME) #if __OBJC2__ -#define JK_DECLARE_STATIC_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME +# define JK_DECLARE_STATIC_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME #else -#define JK_DECLARE_STATIC_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME __asm(".objc_class_name_" # CLASSNAME) +# define JK_DECLARE_STATIC_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME __asm(".objc_class_name_" # CLASSNAME) #endif JK_DECLARE_STATIC_CLASS_REF(NSString); @@ -2648,12 +2648,12 @@ JK_STATIC_INLINE BOOL jk_object_is_string(JKEncodeState *encodeState, id object) if(jk_is_tagged_pointer(object)) { uintptr_t objectTag = jk_get_tagged_pointer_tag(object); - if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(YES); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { encodeState->fastTagLookup.stringClass = objectTag; return(YES); } + if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(YES); } + else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { encodeState->fastTagLookup.stringClass = objectTag; return(YES); } } else { #endif - if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { return(YES); } + if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { return(YES); } #if JK_SUPPORT_TAGGED_POINTERS } #endif @@ -2954,7 +2954,7 @@ - (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags enc jk_managedBuffer_setToStackBuffer(&encodeState->utf8ConversionBuffer, stackUTF8Buffer, sizeof(stackUTF8Buffer)); if(((encodeOption & JKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:JK_STATIC_CLASS_REF(NSArray)] == NO) && ([object isKindOfClass:JK_STATIC_CLASS_REF(NSDictionary)] == NO))) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; } - if(((encodeOption & JKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)] == NO)) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; } + if(((encodeOption & JKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)] == NO)) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; } if(jk_encode_add_atom_to_buffer(encodeState, object) == 0) { BOOL stackBuffer = ((encodeState->stringBuffer.flags & JKManagedBufferMustFree) == 0UL) ? YES : NO; From 44e5ba4ad9857ac079955822c1dd4b89d6d6daa8 Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Sun, 22 Nov 2020 18:50:06 +0100 Subject: [PATCH 13/15] Fixed tagged pointers for Apple Silicon, iOS 14.0, watchOS 7.0 and tvOS 14.0. --- JSONKit.m | 53 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/JSONKit.m b/JSONKit.m index 967cfb5..12b4a40 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -109,6 +109,8 @@ The code in isValidCodePoint() is derived from the ICU code in #import "JSONKit.h" +#include + //#include #include #include @@ -228,12 +230,24 @@ The code in isValidCodePoint() is derived from the ICU code in # define JK_SUPPORT_TAGGED_POINTERS 1 #endif -#if !JK_SUPPORT_TAGGED_POINTERS || !TARGET_OS_IPHONE +#if !JK_SUPPORT_TAGGED_POINTERS || ((TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __x86_64__) # define JK_SUPPORT_MSB_TAGGED_POINTERS 0 #else # define JK_SUPPORT_MSB_TAGGED_POINTERS 1 #endif +#if !JK_SUPPORT_MSB_TAGGED_POINTERS || \ + (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ + (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)) || \ + (defined(__WATCH_OS_VERSION_MIN_REQUIRED) && \ + (__WATCH_OS_VERSION_MIN_REQUIRED < __WATCHOS_7_0)) || \ + (defined(__TV_OS_VERSION_MIN_REQUIRED) && \ + (__TV_OS_VERSION_MIN_REQUIRED < __TVOS_14_0)) +# define JK_SUPPORT_SPLIT_TAGGED_POINTERS 0 +#else +# define JK_SUPPORT_SPLIT_TAGGED_POINTERS 1 +#endif + #define JK_STATIC_CLASS_REF(CLASSNAME) ((Class)&OBJC_CLASS_$_ ## CLASSNAME) #if __OBJC2__ # define JK_DECLARE_STATIC_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME @@ -481,9 +495,6 @@ The code in isValidCodePoint() is derived from the ICU code in struct JKFastTagLookup { uintptr_t stringClass; uintptr_t numberClass; - uintptr_t arrayClass; - uintptr_t dictionaryClass; - uintptr_t nullClass; }; #endif @@ -2593,8 +2604,7 @@ JK_STATIC_INLINE JKHash jk_encode_object_hash(const void *objectPtr) { // XXX XXX XXX XXX #if JK_SUPPORT_TAGGED_POINTERS -JK_STATIC_INLINE BOOL jk_is_tagged_pointer(const void *objectPtr) -{ +JK_STATIC_INLINE BOOL jk_is_tagged_pointer(const void *objectPtr) { #if JK_SUPPORT_MSB_TAGGED_POINTERS return(((intptr_t)objectPtr) < 0); #else @@ -2602,10 +2612,29 @@ JK_STATIC_INLINE BOOL jk_is_tagged_pointer(const void *objectPtr) #endif } -JK_STATIC_INLINE uintptr_t jk_get_tagged_pointer_tag(const void *objectPtr) -{ -#if JK_SUPPORT_MSB_TAGGED_POINTERS +#if !JK_SUPPORT_SPLIT_TAGGED_POINTERS && JK_SUPPORT_MSB_TAGGED_POINTERS +typedef uintptr_t (*jk_get_tagged_pointer_tag_t)(const void *objectPtr); + +static uintptr_t jk_get_split_tagged_pointer_tag(const void *objectPtr) { + return(((uintptr_t)objectPtr) & 0x07); +} + +static uintptr_t jk_get_msb_tagged_pointer_tag(const void *objectPtr) { return(((uintptr_t)objectPtr) >> 60); +} + +static jk_get_tagged_pointer_tag_t jk_get_tagged_pointer_tag_func; +static __attribute__((constructor)) void jk_get_tagged_pointer_tag_init(void) { + if(@available(iOS 14.0, watchOS 7.0, tvOS 14.0, *)) { jk_get_tagged_pointer_tag_func = jk_get_split_tagged_pointer_tag; } + else { jk_get_tagged_pointer_tag_func = jk_get_msb_tagged_pointer_tag; } +} +#endif + +JK_STATIC_INLINE uintptr_t jk_get_tagged_pointer_tag(const void *objectPtr) { +#if JK_SUPPORT_SPLIT_TAGGED_POINTERS + return(((uintptr_t)objectPtr) & 0x07); +#elif JK_SUPPORT_MSB_TAGGED_POINTERS + return jk_get_tagged_pointer_tag_func(objectPtr); #else return(((uintptr_t)objectPtr) & 0x0F); #endif @@ -2619,15 +2648,9 @@ JK_STATIC_INLINE int jk_object_class(JKEncodeState *encodeState, id object) { if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(JKClassString); } else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.numberClass)) { return(JKClassNumber); } - else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.dictionaryClass)) { return(JKClassDictionary); } - else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.arrayClass)) { return(JKClassArray); } - else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.nullClass)) { return(JKClassNull); } else { if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { encodeState->fastTagLookup.stringClass = objectTag; return(JKClassString); } else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSNumber)])) { encodeState->fastTagLookup.numberClass = objectTag; return(JKClassNumber); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSDictionary)])) { encodeState->fastTagLookup.dictionaryClass = objectTag; return(JKClassDictionary); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSArray)])) { encodeState->fastTagLookup.arrayClass = objectTag; return(JKClassArray); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSNull)])) { encodeState->fastTagLookup.nullClass = objectTag; return(JKClassNull); } } } else { From 5a906ed926c21711ecc5b4f548e2406af8febf8b Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Sun, 22 Nov 2020 18:50:39 +0100 Subject: [PATCH 14/15] Revert fast class lookup for the old objc runtime. --- JSONKit.m | 139 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 46 deletions(-) diff --git a/JSONKit.m b/JSONKit.m index 12b4a40..d694573 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -224,42 +224,48 @@ The code in isValidCodePoint() is derived from the ICU code in #define JK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__)) #endif // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) -#if !(__OBJC2__ && __LP64__) -# define JK_SUPPORT_TAGGED_POINTERS 0 +#if defined (__OBJC2__) && __OBJC2__ +#define JK_MODERN_RUNTIME 1 +#else // defined (__OBJC2__) && __OBJC2__ +#define JK_MODERN_RUNTIME 0 +#endif // defined (__OBJC2__) && __OBJC2__ + +#if !(JK_MODERN_RUNTIME && __LP64__) +#define JK_SUPPORT_TAGGED_POINTERS 0 +#else // !(JK_MODERN_RUNTIME && __LP64__) +#define JK_SUPPORT_TAGGED_POINTERS 1 +#endif // !(JK_MODERN_RUNTIME && __LP64__) + +#if !JK_SUPPORT_TAGGED_POINTERS || ((TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __x86_64__) +#define JK_SUPPORT_MSB_TAGGED_POINTERS 0 +#else // !JK_SUPPORT_TAGGED_POINTERS || ((TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __x86_64__) +#define JK_SUPPORT_MSB_TAGGED_POINTERS 1 +#endif // !JK_SUPPORT_TAGGED_POINTERS || ((TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __x86_64__) + +#if !JK_SUPPORT_MSB_TAGGED_POINTERS || \ + (defined (__IPHONE_OS_VERSION_MIN_REQUIRED) && \ + (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)) || \ + (defined (__WATCH_OS_VERSION_MIN_REQUIRED) && \ + (__WATCH_OS_VERSION_MIN_REQUIRED < __WATCHOS_7_0)) || \ + (defined (__TV_OS_VERSION_MIN_REQUIRED) && \ + (__TV_OS_VERSION_MIN_REQUIRED < __TVOS_14_0)) +#define JK_SUPPORT_SPLIT_TAGGED_POINTERS 0 #else -# define JK_SUPPORT_TAGGED_POINTERS 1 +#define JK_SUPPORT_SPLIT_TAGGED_POINTERS 1 #endif -#if !JK_SUPPORT_TAGGED_POINTERS || ((TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __x86_64__) -# define JK_SUPPORT_MSB_TAGGED_POINTERS 0 -#else -# define JK_SUPPORT_MSB_TAGGED_POINTERS 1 -#endif +#if JK_MODERN_RUNTIME +#define JK_DECLARE_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME +#define JK_FAST_CLASS_REF(CLASSNAME) (Class)&OBJC_CLASS_$_ ## CLASSNAME +#define JK_FAST_CLASS_MATCH(OBJ, CLASSNAME) (JK_EXPECT_T(object_getClass(OBJ) == JK_FAST_CLASS_REF(CLASSNAME)) || \ + JK_EXPECT_T([OBJ isKindOfClass:JK_FAST_CLASS_REF(CLASSNAME)])) -#if !JK_SUPPORT_MSB_TAGGED_POINTERS || \ - (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ - (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_14_0)) || \ - (defined(__WATCH_OS_VERSION_MIN_REQUIRED) && \ - (__WATCH_OS_VERSION_MIN_REQUIRED < __WATCHOS_7_0)) || \ - (defined(__TV_OS_VERSION_MIN_REQUIRED) && \ - (__TV_OS_VERSION_MIN_REQUIRED < __TVOS_14_0)) -# define JK_SUPPORT_SPLIT_TAGGED_POINTERS 0 -#else -# define JK_SUPPORT_SPLIT_TAGGED_POINTERS 1 -#endif - -#define JK_STATIC_CLASS_REF(CLASSNAME) ((Class)&OBJC_CLASS_$_ ## CLASSNAME) -#if __OBJC2__ -# define JK_DECLARE_STATIC_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME -#else -# define JK_DECLARE_STATIC_CLASS_REF(CLASSNAME) extern void OBJC_CLASS_$_ ## CLASSNAME __asm(".objc_class_name_" # CLASSNAME) -#endif - -JK_DECLARE_STATIC_CLASS_REF(NSString); -JK_DECLARE_STATIC_CLASS_REF(NSNumber); -JK_DECLARE_STATIC_CLASS_REF(NSDictionary); -JK_DECLARE_STATIC_CLASS_REF(NSArray); -JK_DECLARE_STATIC_CLASS_REF(NSNull); +JK_DECLARE_CLASS_REF(NSString); +JK_DECLARE_CLASS_REF(NSNumber); +JK_DECLARE_CLASS_REF(NSDictionary); +JK_DECLARE_CLASS_REF(NSArray); +JK_DECLARE_CLASS_REF(NSNull); +#endif // JK_MODERN_RUNTIME @class JKArray, JKDictionaryEnumerator, JKDictionary; @@ -385,6 +391,9 @@ The code in isValidCodePoint() is derived from the ICU code in typedef struct JKConstPtrRange JKConstPtrRange; typedef struct JKRange JKRange; typedef struct JKManagedBuffer JKManagedBuffer; +#if !JK_MODERN_RUNTIME +typedef struct JKFastClassLookup JKFastClassLookup; +#endif #if JK_SUPPORT_TAGGED_POINTERS typedef struct JKFastTagLookup JKFastTagLookup; #endif @@ -491,6 +500,16 @@ The code in isValidCodePoint() is derived from the ICU code in BOOL mutableCollections; }; +#if !JK_MODERN_RUNTIME +struct JKFastClassLookup { + void *stringClass; + void *numberClass; + void *arrayClass; + void *dictionaryClass; + void *nullClass; +}; +#endif + #if JK_SUPPORT_TAGGED_POINTERS struct JKFastTagLookup { uintptr_t stringClass; @@ -508,6 +527,9 @@ The code in isValidCodePoint() is derived from the ICU code in JKManagedBuffer utf8ConversionBuffer; JKManagedBuffer stringBuffer; size_t atIndex; +#if !JK_MODERN_RUNTIME + JKFastClassLookup fastClassLookup; +#endif #if JK_SUPPORT_TAGGED_POINTERS JKFastTagLookup fastTagLookup; #endif @@ -2624,6 +2646,7 @@ static uintptr_t jk_get_msb_tagged_pointer_tag(const void *objectPtr) { } static jk_get_tagged_pointer_tag_t jk_get_tagged_pointer_tag_func; + static __attribute__((constructor)) void jk_get_tagged_pointer_tag_init(void) { if(@available(iOS 14.0, watchOS 7.0, tvOS 14.0, *)) { jk_get_tagged_pointer_tag_func = jk_get_split_tagged_pointer_tag; } else { jk_get_tagged_pointer_tag_func = jk_get_msb_tagged_pointer_tag; } @@ -2646,20 +2669,37 @@ JK_STATIC_INLINE int jk_object_class(JKEncodeState *encodeState, id object) { if(jk_is_tagged_pointer(object)) { uintptr_t objectTag = jk_get_tagged_pointer_tag(object); - if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(JKClassString); } - else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.numberClass)) { return(JKClassNumber); } + if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(JKClassString); } + else if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.numberClass)) { return(JKClassNumber); } else { - if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { encodeState->fastTagLookup.stringClass = objectTag; return(JKClassString); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSNumber)])) { encodeState->fastTagLookup.numberClass = objectTag; return(JKClassNumber); } + if(JK_FAST_CLASS_MATCH(object, NSString)) { encodeState->fastTagLookup.stringClass = objectTag; return(JKClassString); } + else if(JK_FAST_CLASS_MATCH(object, NSNumber)) { encodeState->fastTagLookup.numberClass = objectTag; return(JKClassNumber); } } } else { #endif - if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { return(JKClassString); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSNumber)])) { return(JKClassNumber); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSDictionary)])) { return(JKClassDictionary); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSArray)])) { return(JKClassArray); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSNull)])) { return(JKClassNull); } +#if JK_MODERN_RUNTIME + if(JK_FAST_CLASS_MATCH(object, NSString)) { return(JKClassString); } + else if(JK_FAST_CLASS_MATCH(object, NSNumber)) { return(JKClassNumber); } + else if(JK_FAST_CLASS_MATCH(object, NSDictionary)) { return(JKClassDictionary); } + else if(JK_FAST_CLASS_MATCH(object, NSArray)) { return(JKClassArray); } + else if(JK_FAST_CLASS_MATCH(object, NSNull)) { return(JKClassNull); } +#else + void *objectISA = *((void **)object); + + if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.stringClass)) { return(JKClassString); } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.numberClass)) { return(JKClassNumber); } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.dictionaryClass)) { return(JKClassDictionary); } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.arrayClass)) { return(JKClassArray); } + else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.nullClass)) { return(JKClassNull); } + else { + if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastClassLookup.stringClass = objectISA; return(JKClassString); } + else if(JK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { encodeState->fastClassLookup.numberClass = objectISA; return(JKClassNumber); } + else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { encodeState->fastClassLookup.dictionaryClass = objectISA; return(JKClassDictionary); } + else if(JK_EXPECT_T([object isKindOfClass:[NSArray class]])) { encodeState->fastClassLookup.arrayClass = objectISA; return(JKClassArray); } + else if(JK_EXPECT_T([object isKindOfClass:[NSNull class]])) { encodeState->fastClassLookup.nullClass = objectISA; return(JKClassNull); } + } +#endif #if JK_SUPPORT_TAGGED_POINTERS } #endif @@ -2671,12 +2711,19 @@ JK_STATIC_INLINE BOOL jk_object_is_string(JKEncodeState *encodeState, id object) if(jk_is_tagged_pointer(object)) { uintptr_t objectTag = jk_get_tagged_pointer_tag(object); - if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(YES); } - else if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { encodeState->fastTagLookup.stringClass = objectTag; return(YES); } + if(JK_EXPECT_T(objectTag == encodeState->fastTagLookup.stringClass)) { return(YES); } + else if(JK_FAST_CLASS_MATCH(object, NSString)) { encodeState->fastTagLookup.stringClass = objectTag; return(YES); } } else { #endif - if(JK_EXPECT_T([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)])) { return(YES); } +#if JK_MODERN_RUNTIME + if(JK_FAST_CLASS_MATCH(object, NSString)) { return(YES); } +#else + void *objectISA = *((void **)object); + + if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.stringClass)) { return(YES); } + else if(JK_EXPECT_T([object isKindOfClass:[NSString class]])) { encodeState->fastClassLookup.stringClass = objectISA; return(YES); } +#endif #if JK_SUPPORT_TAGGED_POINTERS } #endif @@ -2976,8 +3023,8 @@ - (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags enc unsigned char stackUTF8Buffer[JK_UTF8BUFFER_SIZE] JK_ALIGNED(64); jk_managedBuffer_setToStackBuffer(&encodeState->utf8ConversionBuffer, stackUTF8Buffer, sizeof(stackUTF8Buffer)); - if(((encodeOption & JKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:JK_STATIC_CLASS_REF(NSArray)] == NO) && ([object isKindOfClass:JK_STATIC_CLASS_REF(NSDictionary)] == NO))) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; } - if(((encodeOption & JKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:JK_STATIC_CLASS_REF(NSString)] == NO)) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; } + if(((encodeOption & JKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:[NSArray class]] == NO) && ([object isKindOfClass:[NSDictionary class]] == NO))) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; } + if(((encodeOption & JKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; } if(jk_encode_add_atom_to_buffer(encodeState, object) == 0) { BOOL stackBuffer = ((encodeState->stringBuffer.flags & JKManagedBufferMustFree) == 0UL) ? YES : NO; From 76c192894cad738d0e24e188e979eba279947b01 Mon Sep 17 00:00:00 2001 From: Jean-Charles BERTIN Date: Sun, 22 Nov 2020 18:51:13 +0100 Subject: [PATCH 15/15] Fixed deprecated -getObjects:andKeys: method for JKDictionary class. --- JSONKit.m | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/JSONKit.m b/JSONKit.m index d694573..2841258 100644 --- a/JSONKit.m +++ b/JSONKit.m @@ -1098,18 +1098,26 @@ - (id)objectForKey:(id)aKey return((entryForKey != NULL) ? entryForKey->object : NULL); } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" - (void)getObjects:(id *)objects andKeys:(id *)keys +#pragma clang diagnostic pop { - NSParameterAssert((entry != NULL) && (count <= capacity)); - NSUInteger atEntry = 0UL; NSUInteger arrayIdx = 0UL; - for(atEntry = 0UL; atEntry < capacity; atEntry++) { - if(JK_EXPECT_T(entry[atEntry].key != NULL)) { - NSCParameterAssert((entry[atEntry].object != NULL) && (arrayIdx < count)); - if(JK_EXPECT_T(keys != NULL)) { keys[arrayIdx] = entry[atEntry].key; } - if(JK_EXPECT_T(objects != NULL)) { objects[arrayIdx] = entry[atEntry].object; } - arrayIdx++; + return [self getObjects:objects andKeys:keys count:count]; +} + +- (void)getObjects:(id *)objects andKeys:(id *)keys count:(NSUInteger)arrayCount +{ + NSParameterAssert((entry != NULL) && (count <= capacity) && (arrayCount <= count)); + NSUInteger atEntry = 0UL; NSUInteger arrayIdx = 0UL; + for(atEntry = 0UL; atEntry < capacity && arrayIdx < arrayCount; atEntry++) { + if(JK_EXPECT_T(entry[atEntry].key != NULL)) { + NSCParameterAssert((entry[atEntry].object != NULL) && (arrayIdx < count)); + if(JK_EXPECT_T(keys != NULL)) { keys[arrayIdx] = entry[atEntry].key; } + if(JK_EXPECT_T(objects != NULL)) { objects[arrayIdx] = entry[atEntry].object; } + arrayIdx++; + } } - } } - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len