Commit d6590d65 authored by Thomas Van Lenten's avatar Thomas Van Lenten

Drop all use of OSSpinLock

Apple engineers have pointed out that OSSpinLocks are vulnerable to live locking
on iOS in cases of priority inversion:
. http://mjtsai.com/blog/2015/12/16/osspinlock-is-unsafe/
. https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000372.html

- Use a dispatch_semaphore_t within the extension registry.
- Use a dispatch_semaphore_t for protecting autocreation within messages.
- Drop the custom/internal GPBString class since we don't have really good
  numbers to judge the locking replacements and it isn't required. We can
  always bring it back with real data in the future.
parent afbc89a2
......@@ -468,7 +468,6 @@ objectivec_EXTRA_DIST= \
objectivec/Tests/GPBMessageTests.m \
objectivec/Tests/GPBObjectiveCPlusPlusTest.mm \
objectivec/Tests/GPBPerfTests.m \
objectivec/Tests/GPBStringTests.m \
objectivec/Tests/GPBSwiftTests.swift \
objectivec/Tests/GPBTestUtilities.h \
objectivec/Tests/GPBTestUtilities.m \
......
......@@ -216,7 +216,17 @@ NSString *GPBCodedInputStreamReadRetainedString(
result = @"";
} else {
CheckSize(state, size);
result = GPBCreateGPBStringWithUTF8(&state->bytes[state->bufferPos], size);
result = [[NSString alloc] initWithBytes:&state->bytes[state->bufferPos]
length:size
encoding:NSUTF8StringEncoding];
if (!result) {
result = @"";
#ifdef DEBUG
// https://developers.google.com/protocol-buffers/docs/proto#scalar
NSLog(@"UTF8 failure, is some field type 'string' when it should be "
@"'bytes'?");
#endif
}
state->bufferPos += size;
}
return result;
......@@ -478,320 +488,3 @@ void GPBCodedInputStreamCheckLastTagWas(GPBCodedInputStreamState *state,
}
@end
@implementation GPBString {
@package
CFStringRef string_;
unsigned char *utf8_;
NSUInteger utf8Len_;
// This lock is used to gate access to utf8_. Once GPBStringInitStringValue()
// has been called, string_ will be filled in, and utf8_ will be NULL.
OSSpinLock lock_;
BOOL hasBOM_;
BOOL is7BitAscii_;
}
// Returns true if the passed in bytes are 7 bit ascii.
// This routine needs to be fast.
static bool AreBytesIn7BitASCII(const uint8_t *bytes, NSUInteger len) {
// In the loops below, it's more efficient to collect rather than do
// conditional at every step.
#if __LP64__
// Align bytes. This is especially important in case of 3 byte BOM.
while (len > 0 && ((size_t)bytes & 0x07)) {
if (*bytes++ & 0x80) return false;
len--;
}
while (len >= 32) {
uint64_t val = *(const uint64_t *)bytes;
uint64_t hiBits = (val & 0x8080808080808080ULL);
bytes += 8;
val = *(const uint64_t *)bytes;
hiBits |= (val & 0x8080808080808080ULL);
bytes += 8;
val = *(const uint64_t *)bytes;
hiBits |= (val & 0x8080808080808080ULL);
bytes += 8;
val = *(const uint64_t *)bytes;
if (hiBits | (val & 0x8080808080808080ULL)) return false;
bytes += 8;
len -= 32;
}
while (len >= 16) {
uint64_t val = *(const uint64_t *)bytes;
uint64_t hiBits = (val & 0x8080808080808080ULL);
bytes += 8;
val = *(const uint64_t *)bytes;
if (hiBits | (val & 0x8080808080808080ULL)) return false;
bytes += 8;
len -= 16;
}
while (len >= 8) {
uint64_t val = *(const uint64_t *)bytes;
if (val & 0x8080808080808080ULL) return false;
bytes += 8;
len -= 8;
}
#else // __LP64__
// Align bytes. This is especially important in case of 3 byte BOM.
while (len > 0 && ((size_t)bytes & 0x03)) {
if (*bytes++ & 0x80) return false;
len--;
}
while (len >= 16) {
uint32_t val = *(const uint32_t *)bytes;
uint32_t hiBits = (val & 0x80808080U);
bytes += 4;
val = *(const uint32_t *)bytes;
hiBits |= (val & 0x80808080U);
bytes += 4;
val = *(const uint32_t *)bytes;
hiBits |= (val & 0x80808080U);
bytes += 4;
val = *(const uint32_t *)bytes;
if (hiBits | (val & 0x80808080U)) return false;
bytes += 4;
len -= 16;
}
while (len >= 8) {
uint32_t val = *(const uint32_t *)bytes;
uint32_t hiBits = (val & 0x80808080U);
bytes += 4;
val = *(const uint32_t *)bytes;
if (hiBits | (val & 0x80808080U)) return false;
bytes += 4;
len -= 8;
}
#endif // __LP64__
while (len >= 4) {
uint32_t val = *(const uint32_t *)bytes;
if (val & 0x80808080U) return false;
bytes += 4;
len -= 4;
}
while (len--) {
if (*bytes++ & 0x80) return false;
}
return true;
}
static void GPBStringInitStringValue(GPBString *string) {
OSSpinLockLock(&string->lock_);
GPBStringInitStringValueAlreadyLocked(string);
OSSpinLockUnlock(&string->lock_);
}
static void GPBStringInitStringValueAlreadyLocked(GPBString *string) {
if (string->string_ == NULL && string->utf8_ != NULL) {
// Using kCFAllocatorMalloc for contentsDeallocator, as buffer in
// string->utf8_ is being handed off.
string->string_ = CFStringCreateWithBytesNoCopy(
NULL, string->utf8_, string->utf8Len_, kCFStringEncodingUTF8, false,
kCFAllocatorMalloc);
if (!string->string_) {
#ifdef DEBUG
// https://developers.google.com/protocol-buffers/docs/proto#scalar
NSLog(@"UTF8 failure, is some field type 'string' when it should be "
@"'bytes'?");
#endif
string->string_ = CFSTR("");
string->utf8Len_ = 0;
// On failure, we have to clean up the buffer.
free(string->utf8_);
}
string->utf8_ = NULL;
}
}
GPBString *GPBCreateGPBStringWithUTF8(const void *bytes, NSUInteger length) {
GPBString *result = [[GPBString alloc] initWithBytes:bytes length:length];
return result;
}
- (instancetype)initWithBytes:(const void *)bytes length:(NSUInteger)length {
self = [super init];
if (self) {
utf8_ = malloc(length);
memcpy(utf8_, bytes, length);
utf8Len_ = length;
lock_ = OS_SPINLOCK_INIT;
is7BitAscii_ = AreBytesIn7BitASCII(bytes, length);
if (length >= 3 && memcmp(utf8_, "\xef\xbb\xbf", 3) == 0) {
// We can't just remove the BOM from the string here, because in the case
// where we have > 1 BOM at the beginning of the string, we will remove one,
// and the internal NSString we create will remove the next one, and we will
// end up with a GPBString != NSString issue.
// We also just can't remove all the BOMs because then we would end up with
// potential cases where a GPBString and an NSString made with the same
// UTF8 buffer would in fact be different.
// We record the fact we have a BOM, and use it as necessary to simulate
// what NSString would return for various calls.
hasBOM_ = YES;
#if DEBUG
// Sending BOMs across the line is just wasting bits.
NSLog(@"Bad data? String should not have BOM!");
#endif // DEBUG
}
}
return self;
}
- (void)dealloc {
if (string_ != NULL) {
CFRelease(string_);
}
if (utf8_ != NULL) {
free(utf8_);
}
[super dealloc];
}
// Required NSString overrides.
- (NSUInteger)length {
if (is7BitAscii_) {
return utf8Len_;
} else {
GPBStringInitStringValue(self);
return CFStringGetLength(string_);
}
}
- (unichar)characterAtIndex:(NSUInteger)anIndex {
OSSpinLockLock(&lock_);
if (is7BitAscii_ && utf8_) {
unichar result = utf8_[anIndex];
OSSpinLockUnlock(&lock_);
return result;
} else {
GPBStringInitStringValueAlreadyLocked(self);
OSSpinLockUnlock(&lock_);
return CFStringGetCharacterAtIndex(string_, anIndex);
}
}
// Override a couple of methods that typically want high performance.
- (id)copyWithZone:(NSZone *)zone {
GPBStringInitStringValue(self);
return [(NSString *)string_ copyWithZone:zone];
}
- (id)mutableCopyWithZone:(NSZone *)zone {
GPBStringInitStringValue(self);
return [(NSString *)string_ mutableCopyWithZone:zone];
}
- (NSUInteger)hash {
// Must convert to string here to make sure that the hash is always
// consistent no matter what state the GPBString is in.
GPBStringInitStringValue(self);
return CFHash(string_);
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if ([object isKindOfClass:[NSString class]]) {
GPBStringInitStringValue(self);
return CFStringCompare(string_, (CFStringRef)object, 0) ==
kCFCompareEqualTo;
}
return NO;
}
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange {
OSSpinLockLock(&lock_);
if (is7BitAscii_ && utf8_) {
unsigned char *bytes = &(utf8_[aRange.location]);
for (NSUInteger i = 0; i < aRange.length; ++i) {
buffer[i] = bytes[i];
}
OSSpinLockUnlock(&lock_);
} else {
GPBStringInitStringValueAlreadyLocked(self);
OSSpinLockUnlock(&lock_);
CFStringGetCharacters(string_, CFRangeMake(aRange.location, aRange.length),
buffer);
}
}
- (NSUInteger)lengthOfBytesUsingEncoding:(NSStringEncoding)encoding {
if ((encoding == NSUTF8StringEncoding) ||
(encoding == NSASCIIStringEncoding && is7BitAscii_)) {
return utf8Len_ - (hasBOM_ ? 3 : 0);
} else {
GPBStringInitStringValue(self);
return [(NSString *)string_ lengthOfBytesUsingEncoding:encoding];
}
}
- (BOOL)getBytes:(void *)buffer
maxLength:(NSUInteger)maxLength
usedLength:(NSUInteger *)usedLength
encoding:(NSStringEncoding)encoding
options:(NSStringEncodingConversionOptions)options
range:(NSRange)range
remainingRange:(NSRangePointer)remainingRange {
// [NSString getBytes:maxLength:usedLength:encoding:options:range:remainingRange]
// does not return reliable results if the maxLength argument is 0
// (Radar 16385183). Therefore we have special cased it as a slow case so
// that it behaves however Apple intends it to behave. It should be a rare
// case.
//
// [NSString getBytes:maxLength:usedLength:encoding:options:range:remainingRange]
// does not return reliable results if the range is outside of the strings
// length (Radar 16396177). Therefore we have special cased it as a slow
// case so that it behaves however Apple intends it to behave. It should
// be a rare case.
//
// We can optimize the UTF8StringEncoding and NSASCIIStringEncoding with no
// options cases.
if ((options == 0) &&
(encoding == NSUTF8StringEncoding || encoding == NSASCIIStringEncoding) &&
(maxLength != 0) &&
(NSMaxRange(range) <= utf8Len_)) {
// Might be able to optimize it.
OSSpinLockLock(&lock_);
if (is7BitAscii_ && utf8_) {
NSUInteger length = range.length;
length = (length < maxLength) ? length : maxLength;
memcpy(buffer, utf8_ + range.location, length);
if (usedLength) {
*usedLength = length;
}
if (remainingRange) {
remainingRange->location = range.location + length;
remainingRange->length = range.length - length;
}
OSSpinLockUnlock(&lock_);
if (length > 0) {
return YES;
} else {
return NO;
}
} else {
GPBStringInitStringValueAlreadyLocked(self);
OSSpinLockUnlock(&lock_);
}
} else {
GPBStringInitStringValue(self);
}
return [(NSString *)string_ getBytes:buffer
maxLength:maxLength
usedLength:usedLength
encoding:encoding
options:options
range:range
remainingRange:remainingRange];
}
@end
......@@ -39,19 +39,6 @@
@class GPBUnknownFieldSet;
@class GPBFieldDescriptor;
// GPBString is a string subclass that avoids the overhead of initializing
// a full NSString until it is actually needed. Lots of protocol buffers contain
// strings, and instantiating all of those strings and having them parsed to
// verify correctness when the message was being read was expensive, when many
// of the strings were never being used.
//
// Note for future-self. I tried implementing this using a NSProxy.
// Turned out the performance was horrible in client apps because folks
// like to use libraries like SBJSON that grab characters one at a time.
// The proxy overhead was a killer.
@interface GPBString : NSString
@end
typedef struct GPBCodedInputStreamState {
const uint8_t *bytes;
size_t bufferSize;
......@@ -92,10 +79,6 @@ typedef struct GPBCodedInputStreamState {
CF_EXTERN_C_BEGIN
// Returns a GPBString with a +1 retain count.
GPBString *GPBCreateGPBStringWithUTF8(const void *bytes, NSUInteger length)
__attribute__((ns_returns_retained));
int32_t GPBCodedInputStreamReadTag(GPBCodedInputStreamState *state);
double GPBCodedInputStreamReadDouble(GPBCodedInputStreamState *state);
......
......@@ -568,13 +568,13 @@ static id GetArrayIvarWithField(GPBMessage *self, GPBFieldDescriptor *field) {
id array = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
if (!array) {
// Check again after getting the lock.
OSSpinLockLock(&self->readOnlyMutex_);
dispatch_semaphore_wait(self->readOnlySemaphore_, DISPATCH_TIME_FOREVER);
array = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
if (!array) {
array = CreateArrayForField(field, self);
GPBSetAutocreatedRetainedObjectIvarWithField(self, field, array);
}
OSSpinLockUnlock(&self->readOnlyMutex_);
dispatch_semaphore_signal(self->readOnlySemaphore_);
}
return array;
}
......@@ -598,13 +598,13 @@ static id GetMapIvarWithField(GPBMessage *self, GPBFieldDescriptor *field) {
id dict = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
if (!dict) {
// Check again after getting the lock.
OSSpinLockLock(&self->readOnlyMutex_);
dispatch_semaphore_wait(self->readOnlySemaphore_, DISPATCH_TIME_FOREVER);
dict = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
if (!dict) {
dict = CreateMapForField(field, self);
GPBSetAutocreatedRetainedObjectIvarWithField(self, field, dict);
}
OSSpinLockUnlock(&self->readOnlyMutex_);
dispatch_semaphore_signal(self->readOnlySemaphore_);
}
return dict;
}
......@@ -810,7 +810,7 @@ static GPBUnknownFieldSet *GetOrMakeUnknownFields(GPBMessage *self) {
messageStorage_ = (GPBMessage_StoragePtr)(
((uint8_t *)self) + class_getInstanceSize([self class]));
readOnlyMutex_ = OS_SPINLOCK_INIT;
readOnlySemaphore_ = dispatch_semaphore_create(1);
}
return self;
......@@ -1723,7 +1723,7 @@ static GPBUnknownFieldSet *GetOrMakeUnknownFields(GPBMessage *self) {
}
// Check for an autocreated value.
OSSpinLockLock(&readOnlyMutex_);
dispatch_semaphore_wait(readOnlySemaphore_, DISPATCH_TIME_FOREVER);
value = [autocreatedExtensionMap_ objectForKey:extension];
if (!value) {
// Auto create the message extensions to match normal fields.
......@@ -1740,7 +1740,7 @@ static GPBUnknownFieldSet *GetOrMakeUnknownFields(GPBMessage *self) {
[value release];
}
OSSpinLockUnlock(&readOnlyMutex_);
dispatch_semaphore_signal(readOnlySemaphore_);
return value;
}
......
......@@ -62,7 +62,12 @@ typedef struct GPBMessage_Storage *GPBMessage_StoragePtr;
// by *read* operations such as getters (autocreation of message fields and
// message extensions, not setting of values). Used to guarantee thread safety
// for concurrent reads on the message.
OSSpinLock readOnlyMutex_;
// NOTE: OSSpinLock may seem like a good fit here but Apple engineers have
// pointed out that they are vulnerable to live locking on iOS in cases of
// priority inversion:
// http://mjtsai.com/blog/2015/12/16/osspinlock-is-unsafe/
// https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000372.html
dispatch_semaphore_t readOnlySemaphore_;
}
// Gets an extension value without autocreating the result if not found. (i.e.
......
......@@ -31,7 +31,6 @@
#import "GPBRootObject_PackagePrivate.h"
#import <objc/runtime.h>
#import <libkern/OSAtomic.h>
#import <CoreFoundation/CoreFoundation.h>
......@@ -96,13 +95,19 @@ static CFHashCode GPBRootExtensionKeyHash(const void *value) {
return jenkins_one_at_a_time_hash(key);
}
static OSSpinLock gExtensionSingletonDictionaryLock_ = OS_SPINLOCK_INIT;
// NOTE: OSSpinLock may seem like a good fit here but Apple engineers have
// pointed out that they are vulnerable to live locking on iOS in cases of
// priority inversion:
// http://mjtsai.com/blog/2015/12/16/osspinlock-is-unsafe/
// https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000372.html
static dispatch_semaphore_t gExtensionSingletonDictionarySemaphore;
static CFMutableDictionaryRef gExtensionSingletonDictionary = NULL;
static GPBExtensionRegistry *gDefaultExtensionRegistry = NULL;
+ (void)initialize {
// Ensure the global is started up.
if (!gExtensionSingletonDictionary) {
gExtensionSingletonDictionarySemaphore = dispatch_semaphore_create(1);
CFDictionaryKeyCallBacks keyCallBacks = {
// See description above for reason for using custom dictionary.
0,
......@@ -134,9 +139,10 @@ static GPBExtensionRegistry *gDefaultExtensionRegistry = NULL;
+ (void)globallyRegisterExtension:(GPBExtensionDescriptor *)field {
const char *key = [field singletonNameC];
OSSpinLockLock(&gExtensionSingletonDictionaryLock_);
dispatch_semaphore_wait(gExtensionSingletonDictionarySemaphore,
DISPATCH_TIME_FOREVER);
CFDictionarySetValue(gExtensionSingletonDictionary, key, field);
OSSpinLockUnlock(&gExtensionSingletonDictionaryLock_);
dispatch_semaphore_signal(gExtensionSingletonDictionarySemaphore);
}
static id ExtensionForName(id self, SEL _cmd) {
......@@ -166,14 +172,24 @@ static id ExtensionForName(id self, SEL _cmd) {
key[classNameLen] = '_';
memcpy(&key[classNameLen + 1], selName, selNameLen);
key[classNameLen + 1 + selNameLen] = '\0';
OSSpinLockLock(&gExtensionSingletonDictionaryLock_);
// NOTE: Even though this method is called from another C function,
// gExtensionSingletonDictionarySemaphore and gExtensionSingletonDictionary
// will always be initialized. This is because this call flow is just to
// lookup the Extension, meaning the code is calling an Extension class
// message on a Message or Root class. This guarantees that the class was
// initialized and Message classes ensure their Root was also initialized.
NSAssert(gExtensionSingletonDictionary, @"Startup order broken!");
dispatch_semaphore_wait(gExtensionSingletonDictionarySemaphore,
DISPATCH_TIME_FOREVER);
id extension = (id)CFDictionaryGetValue(gExtensionSingletonDictionary, key);
if (extension) {
// The method is getting wired in to the class, so no need to keep it in
// the dictionary.
CFDictionaryRemoveValue(gExtensionSingletonDictionary, key);
}
OSSpinLockUnlock(&gExtensionSingletonDictionaryLock_);
dispatch_semaphore_signal(gExtensionSingletonDictionarySemaphore);
return extension;
}
......
......@@ -411,7 +411,7 @@ id GPBGetObjectIvarWithField(GPBMessage *self, GPBFieldDescriptor *field) {
return field.defaultValue.valueMessage;
}
OSSpinLockLock(&self->readOnlyMutex_);
dispatch_semaphore_wait(self->readOnlySemaphore_, DISPATCH_TIME_FOREVER);
GPBMessage *result = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
if (!result) {
// For non repeated messages, create the object, set it and return it.
......@@ -420,7 +420,7 @@ id GPBGetObjectIvarWithField(GPBMessage *self, GPBFieldDescriptor *field) {
result = GPBCreateMessageWithAutocreator(field.msgClass, self, field);
GPBSetAutocreatedRetainedObjectIvarWithField(self, field, result);
}
OSSpinLockUnlock(&self->readOnlyMutex_);
dispatch_semaphore_signal(self->readOnlySemaphore_);
return result;
}
......
......@@ -28,7 +28,6 @@
8B8B615D17DF7056002EE618 /* GPBARCUnittestProtos.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B8B615C17DF7056002EE618 /* GPBARCUnittestProtos.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
8B96157414C8C38C00A2AC0B /* GPBDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B96157314C8C38C00A2AC0B /* GPBDescriptor.m */; };
8B96157514CA019D00A2AC0B /* Descriptor.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BD3982214BE5B0C0081D629 /* Descriptor.pbobjc.m */; };
8BA9364518DA5F4C0056FA2A /* GPBStringTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BA9364418DA5F4B0056FA2A /* GPBStringTests.m */; };
8BBEA4A9147C727D00C4ADB7 /* GPBCodedInputStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7461B69B0F94FDF800A0C422 /* GPBCodedInputStreamTests.m */; };
8BBEA4AA147C727D00C4ADB7 /* GPBCodedOuputStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7461B69D0F94FDF800A0C422 /* GPBCodedOuputStreamTests.m */; };
8BBEA4AC147C727D00C4ADB7 /* GPBMessageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7461B6A30F94FDF800A0C422 /* GPBMessageTests.m */; };
......@@ -158,7 +157,6 @@
8B8B615C17DF7056002EE618 /* GPBARCUnittestProtos.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GPBARCUnittestProtos.m; sourceTree = "<group>"; };
8B96157214C8B06000A2AC0B /* GPBDescriptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GPBDescriptor.h; sourceTree = "<group>"; };
8B96157314C8C38C00A2AC0B /* GPBDescriptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GPBDescriptor.m; sourceTree = "<group>"; };
8BA9364418DA5F4B0056FA2A /* GPBStringTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GPBStringTests.m; sourceTree = "<group>"; };
8BBD9DB016DD1DC8008E1EC1 /* unittest_lite.proto */ = {isa = PBXFileReference; lastKnownFileType = text; name = unittest_lite.proto; path = ../../src/google/protobuf/unittest_lite.proto; sourceTree = "<group>"; };
8BBEA4A6147C727100C4ADB7 /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8BCF338814ED799900BC5317 /* GPBProtocolBuffers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GPBProtocolBuffers.m; sourceTree = "<group>"; };
......@@ -419,7 +417,6 @@
F4487C7E1AAF62CD00531423 /* GPBMessageTests+Serialization.m */,
F4B51B1D1BBC610700744318 /* GPBObjectiveCPlusPlusTest.mm */,
F41C175C1833D3310064ED4D /* GPBPerfTests.m */,
8BA9364418DA5F4B0056FA2A /* GPBStringTests.m */,
8B4248BA1A8C256A00BC1EC6 /* GPBSwiftTests.swift */,
7461B6AB0F94FDF800A0C422 /* GPBTestUtilities.h */,
7461B6AC0F94FDF800A0C422 /* GPBTestUtilities.m */,
......@@ -689,7 +686,6 @@
F41C175D1833D3310064ED4D /* GPBPerfTests.m in Sources */,
F4353D341AC06F10005A6198 /* GPBDictionaryTests+Bool.m in Sources */,
F4487C831AAF6AB300531423 /* GPBMessageTests+Merge.m in Sources */,
8BA9364518DA5F4C0056FA2A /* GPBStringTests.m in Sources */,
8BBEA4B6147C727D00C4ADB7 /* GPBUnknownFieldSetTest.m in Sources */,
F4353D371AC06F10005A6198 /* GPBDictionaryTests+String.m in Sources */,
F4353D381AC06F10005A6198 /* GPBDictionaryTests+UInt32.m in Sources */,
......
......@@ -36,7 +36,6 @@
8B9A5EB41831993600A9D33B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B9A5EB31831993600A9D33B /* AppDelegate.m */; };
8B9A5EB61831993600A9D33B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B9A5EB51831993600A9D33B /* Images.xcassets */; };
8B9A5EEC18330A0F00A9D33B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9A5E9F1831913D00A9D33B /* UIKit.framework */; };
8BA9364518DA5F4C0056FA2A /* GPBStringTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BA9364418DA5F4B0056FA2A /* GPBStringTests.m */; };
8BBEA4A9147C727D00C4ADB7 /* GPBCodedInputStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7461B69B0F94FDF800A0C422 /* GPBCodedInputStreamTests.m */; };
8BBEA4AA147C727D00C4ADB7 /* GPBCodedOuputStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7461B69D0F94FDF800A0C422 /* GPBCodedOuputStreamTests.m */; };
8BBEA4AC147C727D00C4ADB7 /* GPBMessageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7461B6A30F94FDF800A0C422 /* GPBMessageTests.m */; };
......@@ -179,7 +178,6 @@
8B9A5EAD1831993600A9D33B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
8B9A5EB31831993600A9D33B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
8B9A5EB51831993600A9D33B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
8BA9364418DA5F4B0056FA2A /* GPBStringTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GPBStringTests.m; sourceTree = "<group>"; };
8BBD9DB016DD1DC8008E1EC1 /* unittest_lite.proto */ = {isa = PBXFileReference; lastKnownFileType = text; name = unittest_lite.proto; path = ../../src/google/protobuf/unittest_lite.proto; sourceTree = "<group>"; };
8BBEA4A6147C727100C4ADB7 /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8BCF338814ED799900BC5317 /* GPBProtocolBuffers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GPBProtocolBuffers.m; sourceTree = "<group>"; };
......@@ -457,7 +455,6 @@
F4487C801AAF62FC00531423 /* GPBMessageTests+Serialization.m */,
F4B51B1B1BBC5C7100744318 /* GPBObjectiveCPlusPlusTest.mm */,
F41C175C1833D3310064ED4D /* GPBPerfTests.m */,
8BA9364418DA5F4B0056FA2A /* GPBStringTests.m */,
8B4248B31A8BD96E00BC1EC6 /* GPBSwiftTests.swift */,
7461B6AB0F94FDF800A0C422 /* GPBTestUtilities.h */,
7461B6AC0F94FDF800A0C422 /* GPBTestUtilities.m */,
......@@ -785,7 +782,6 @@
F41C175D1833D3310064ED4D /* GPBPerfTests.m in Sources */,
F4353D421AC06F31005A6198 /* GPBDictionaryTests+Bool.m in Sources */,
F4487C851AAF6AC500531423 /* GPBMessageTests+Merge.m in Sources */,
8BA9364518DA5F4C0056FA2A /* GPBStringTests.m in Sources */,
8BBEA4B6147C727D00C4ADB7 /* GPBUnknownFieldSetTest.m in Sources */,
F4353D451AC06F31005A6198 /* GPBDictionaryTests+String.m in Sources */,
F4353D461AC06F31005A6198 /* GPBDictionaryTests+UInt32.m in Sources */,
......
......@@ -266,6 +266,9 @@
}
// Verifies fix for b/10315336.
// Note: Now that there isn't a custom string class under the hood, this test
// isn't as critical, but it does cover bad input and if a custom class is added
// again, it will help validate that class' handing of bad utf8.
- (void)testReadMalformedString {
NSOutputStream* rawOutput = [NSOutputStream outputStreamToMemory];
GPBCodedOutputStream* output =
......@@ -276,7 +279,7 @@
[output writeRawVarint32:tag];
[output writeRawVarint32:5];
// Create an invalid utf-8 byte array.
uint8_t bytes[5] = {0xc2, 0xf2};
uint8_t bytes[] = {0xc2, 0xf2, 0x0, 0x0, 0x0};
[output writeRawData:[NSData dataWithBytes:bytes length:sizeof(bytes)]];
[output flush];
......@@ -286,6 +289,7 @@
TestAllTypes* message = [TestAllTypes parseFromCodedInputStream:input
extensionRegistry:nil
error:NULL];
XCTAssertNotNil(message);
// Make sure we can read string properties twice without crashing.
XCTAssertEqual([message.defaultString length], (NSUInteger)0);
XCTAssertEqualObjects(@"", message.defaultString);
......
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <XCTest/XCTest.h>
#import "GPBCodedInputStream_PackagePrivate.h"
#import "GPBTestUtilities.h"
@interface TestClass : NSObject
@property(nonatomic, retain) NSString *foo;
@end
@implementation TestClass
@synthesize foo;
@end
@interface GPBStringTests : XCTestCase {
NSMutableArray *nsStrings_;
NSMutableArray *gpbStrings_;
}
@end
@implementation GPBStringTests
- (void)setUp {
[super setUp];
const char *strings[] = {
"ascii string",
"non-ascii string \xc3\xa9", // e with acute accent
"\xe2\x99\xa1", // White Heart
"mix \xe2\x99\xa4 string", // White Spade
// Decomposed forms from http://www.unicode.org/reports/tr15/
// 1.2 Singletons
"\xe2\x84\xa8 = A\xcc\x8a = \xc3\x85", "\xe2\x84\xa6 = \xce\xa9",
// 1.2 Canonical Composites
"A\xcc\x8a = \xc3\x85",
"o\xcc\x82 = \xc3\xb4",
// 1.2 Multiple Combining Marks
"s\xcc\xa3\xcc\x87 = \xe1\xb9\xa9",
"\xe1\xb8\x8b\xcc\xa3 = d\xcc\xa3\xcc\x87 = \xe1\xb8\x8d \xcc\x87",
"q\xcc\x87\xcc\xa3 = q\xcc\xa3\xcc\x87",
// BOM
"\xEF\xBB\xBF String with BOM",
"String with \xEF\xBB\xBF in middle",
"String with end bom \xEF\xBB\xBF",
"\xEF\xBB\xBF\xe2\x99\xa1", // BOM White Heart
"\xEF\xBB\xBF\xEF\xBB\xBF String with Two BOM",
// Supplementary Plane
"\xf0\x9d\x84\x9e", // MUSICAL SYMBOL G CLEF
// Tags
"\xf3\xa0\x80\x81", // Language Tag
// Variation Selectors
"\xf3\xa0\x84\x80", // VARIATION SELECTOR-17
// Specials
"\xef\xbb\xbf\xef\xbf\xbd\xef\xbf\xbf",
// Left To Right/Right To Left
// http://unicode.org/reports/tr9/
// Hello! <RTL marker>!Merhaba<LTR marker>
"Hello! \xE2\x80\x8F!\xd9\x85\xd8\xb1\xd8\xad\xd8\xa8\xd8\xa7\xE2\x80\x8E",
"\xE2\x80\x8E LTR At Start",
"LTR At End\xE2\x80\x8E",
"\xE2\x80\x8F RTL At Start",
"RTL At End\xE2\x80\x8F",
"\xE2\x80\x8E\xE2\x80\x8E Double LTR \xE2\x80\x8E\xE2\x80\x8E",
"\xE2\x80\x8F\xE2\x80\x8F Double RTL \xE2\x80\x8F\xE2\x80\x8F",
"\xE2\x80\x8F\xE2\x80\x8E LTR-RTL LTR-RTL \xE2\x80\x8E\xE2\x80\x8F",
};
size_t stringsSize = GPBARRAYSIZE(strings);
size_t numberUnicodeStrings = 17375;
nsStrings_ = [[NSMutableArray alloc]
initWithCapacity:stringsSize + numberUnicodeStrings];
gpbStrings_ = [[NSMutableArray alloc]
initWithCapacity:stringsSize + numberUnicodeStrings];
for (size_t i = 0; i < stringsSize; ++i) {
size_t length = strlen(strings[i]);
NSString *nsString = [[NSString alloc] initWithBytes:strings[i]
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
GPBString *gpbString = GPBCreateGPBStringWithUTF8(strings[i], length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
}
// Generate all UTF8 characters in a variety of strings
// UTF8-1 - 1 Byte UTF 8 chars
int length = 0x7F + 1;
char *buffer = (char *)calloc(length, 1);
for (int i = 0; i < length; ++i) {
buffer[i] = (char)i;
}
NSString *nsString = [[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
GPBString *gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
// UTF8-2 - 2 Byte UTF 8 chars
int pointLength = 0xbf - 0x80 + 1;
length = pointLength * 2;
buffer = (char *)calloc(length, 1);
for (int i = 0xc2; i <= 0xdf; ++i) {
char *bufferPtr = buffer;
for (int j = 0x80; j <= 0xbf; ++j) {
(*bufferPtr++) = (char)i;
(*bufferPtr++) = (char)j;
}
nsString = [[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
}
free(buffer);
// UTF8-3 - 3 Byte UTF 8 chars
length = pointLength * 3;
buffer = (char *)calloc(length, 1);
for (int i = 0xa0; i <= 0xbf; ++i) {
char *bufferPtr = buffer;
for (int j = 0x80; j <= 0xbf; ++j) {
(*bufferPtr++) = (char)0xE0;
(*bufferPtr++) = (char)i;
(*bufferPtr++) = (char)j;
}
nsString = [[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
}
for (int i = 0xe1; i <= 0xec; ++i) {
for (int j = 0x80; j <= 0xbf; ++j) {
char *bufferPtr = buffer;
for (int k = 0x80; k <= 0xbf; ++k) {
(*bufferPtr++) = (char)i;
(*bufferPtr++) = (char)j;
(*bufferPtr++) = (char)k;
}
nsString = [[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
}
}
for (int i = 0x80; i <= 0x9f; ++i) {
char *bufferPtr = buffer;
for (int j = 0x80; j <= 0xbf; ++j) {
(*bufferPtr++) = (char)0xED;
(*bufferPtr++) = (char)i;
(*bufferPtr++) = (char)j;
}
nsString = [[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
}
for (int i = 0xee; i <= 0xef; ++i) {
for (int j = 0x80; j <= 0xbf; ++j) {
char *bufferPtr = buffer;
for (int k = 0x80; k <= 0xbf; ++k) {
(*bufferPtr++) = (char)i;
(*bufferPtr++) = (char)j;
(*bufferPtr++) = (char)k;
}
nsString = [[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
}
}
free(buffer);
// UTF8-4 - 4 Byte UTF 8 chars
length = pointLength * 4;
buffer = (char *)calloc(length, 1);
for (int i = 0x90; i <= 0xbf; ++i) {
for (int j = 0x80; j <= 0xbf; ++j) {
char *bufferPtr = buffer;
for (int k = 0x80; k <= 0xbf; ++k) {
(*bufferPtr++) = (char)0xF0;
(*bufferPtr++) = (char)i;
(*bufferPtr++) = (char)j;
(*bufferPtr++) = (char)k;
}
nsString = [[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
}
}
for (int i = 0xf1; i <= 0xf3; ++i) {
for (int j = 0x80; j <= 0xbf; ++j) {
for (int k = 0x80; k <= 0xbf; ++k) {
char *bufferPtr = buffer;
for (int m = 0x80; m <= 0xbf; ++m) {
(*bufferPtr++) = (char)i;
(*bufferPtr++) = (char)j;
(*bufferPtr++) = (char)k;
(*bufferPtr++) = (char)m;
}
nsString = [[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
}
}
}
for (int i = 0x80; i <= 0x8f; ++i) {
for (int j = 0x80; j <= 0xbf; ++j) {
char *bufferPtr = buffer;
for (int k = 0x80; k <= 0xbf; ++k) {
(*bufferPtr++) = (char)0xF4;
(*bufferPtr++) = (char)i;
(*bufferPtr++) = (char)j;
(*bufferPtr++) = (char)k;
}
nsString = [[NSString alloc] initWithBytes:buffer
length:length
encoding:NSUTF8StringEncoding];
[nsStrings_ addObject:nsString];
[nsString release];
gpbString = GPBCreateGPBStringWithUTF8(buffer, length);
[gpbStrings_ addObject:gpbString];
[gpbString release];
}
}
free(buffer);
}
- (void)tearDown {
[nsStrings_ release];
[gpbStrings_ release];
[super tearDown];
}
- (void)testLength {
size_t i = 0;
for (NSString *nsString in nsStrings_) {
GPBString *gpbString = gpbStrings_[i];
XCTAssertEqual([nsString length], [gpbString length], @"%@ %@", nsString,
gpbString);
++i;
}
}
- (void)testLengthOfBytesUsingEncoding {
NSStringEncoding encodings[] = {
NSUTF8StringEncoding,
NSASCIIStringEncoding,
NSISOLatin1StringEncoding,
NSMacOSRomanStringEncoding,
NSUTF16StringEncoding,
NSUTF32StringEncoding,
};
for (size_t j = 0; j < GPBARRAYSIZE(encodings); ++j) {
NSStringEncoding testEncoding = encodings[j];
size_t i = 0;
for (NSString *nsString in nsStrings_) {
GPBString *gpbString = gpbStrings_[i];
XCTAssertEqual([nsString lengthOfBytesUsingEncoding:testEncoding],
[gpbString lengthOfBytesUsingEncoding:testEncoding],
@"%@ %@", nsString, gpbString);
++i;
}
}
}
- (void)testHash {
size_t i = 0;
for (NSString *nsString in nsStrings_) {
GPBString *gpbString = gpbStrings_[i];
XCTAssertEqual([nsString hash], [gpbString hash], @"%@ %@", nsString,
gpbString);
++i;
}
}
- (void)testEquality {
size_t i = 0;
for (NSString *nsString in nsStrings_) {
GPBString *gpbString = gpbStrings_[i];
XCTAssertEqualObjects(nsString, gpbString);
++i;
}
}
- (void)testCharacterAtIndex {
size_t i = 0;
for (NSString *nsString in nsStrings_) {
GPBString *gpbString = gpbStrings_[i];
NSUInteger length = [nsString length];
for (size_t j = 0; j < length; ++j) {
unichar nsChar = [nsString characterAtIndex:j];
unichar pbChar = [gpbString characterAtIndex:j];
XCTAssertEqual(nsChar, pbChar, @"%@ %@ %zu", nsString, gpbString, j);
}
++i;
}
}
- (void)testCopy {
size_t i = 0;
for (NSString *nsString in nsStrings_) {
GPBString *gpbString = [[gpbStrings_[i] copy] autorelease];
XCTAssertEqualObjects(nsString, gpbString);
++i;
}
}
- (void)testMutableCopy {
size_t i = 0;
for (NSString *nsString in nsStrings_) {
GPBString *gpbString = [[gpbStrings_[i] mutableCopy] autorelease];
XCTAssertEqualObjects(nsString, gpbString);
++i;
}
}
- (void)testGetBytes {
// Do an attempt at a reasonably exhaustive test of get bytes.
// Get bytes with options other than 0 should always fall through to Apple
// code so we don't bother testing that path.
size_t i = 0;
char pbBuffer[256];
char nsBuffer[256];
int count = 0;
for (NSString *nsString in nsStrings_) {
GPBString *gpbString = gpbStrings_[i];
for (int j = 0; j < 100; ++j) {
// [NSString getBytes:maxLength:usedLength:encoding:options:range:remainingRange]
// does not return reliable results if the maxLength argument is 0,
// or range is 0,0.
// Radar 16385183
NSUInteger length = [nsString length];
NSUInteger maxBufferCount = (arc4random() % (length + 3)) + 1;
NSUInteger rangeStart = arc4random() % length;
NSUInteger rangeLength = arc4random() % (length - rangeStart);
NSRange range = NSMakeRange(rangeStart, rangeLength);
NSStringEncoding encodings[] = {
NSASCIIStringEncoding,
NSUTF8StringEncoding,
NSUTF16StringEncoding,
};
for (size_t k = 0; k < GPBARRAYSIZE(encodings); ++k) {
NSStringEncoding encoding = encodings[k];
NSUInteger pbUsedBufferCount = 0;
NSUInteger nsUsedBufferCount = 0;
NSRange pbLeftOver = NSMakeRange(0, 0);
NSRange nsLeftOver = NSMakeRange(0, 0);
BOOL pbGotBytes = [gpbString getBytes:pbBuffer
maxLength:maxBufferCount
usedLength:&pbUsedBufferCount
encoding:encoding
options:0
range:range
remainingRange:&pbLeftOver];
BOOL nsGotBytes = [nsString getBytes:nsBuffer
maxLength:maxBufferCount
usedLength:&nsUsedBufferCount
encoding:encoding
options:0
range:range
remainingRange:&nsLeftOver];
XCTAssertEqual(
(bool)pbGotBytes, (bool)nsGotBytes,
@"PB %d '%@' vs '%@' Encoding:%tu MaxLength: %tu Range: %@ "
@"Used: %tu, %tu LeftOver %@, %@)",
count, gpbString, nsString, encoding, maxBufferCount,
NSStringFromRange(range), pbUsedBufferCount, nsUsedBufferCount,
NSStringFromRange(pbLeftOver), NSStringFromRange(nsLeftOver));
XCTAssertEqual(
pbUsedBufferCount, nsUsedBufferCount,
@"PB %d '%@' vs '%@' Encoding:%tu MaxLength: %tu Range: %@ "
@"Used: %tu, %tu LeftOver %@, %@)",
count, gpbString, nsString, encoding, maxBufferCount,
NSStringFromRange(range), pbUsedBufferCount, nsUsedBufferCount,
NSStringFromRange(pbLeftOver), NSStringFromRange(nsLeftOver));
XCTAssertEqual(
pbLeftOver.location, nsLeftOver.location,
@"PB %d '%@' vs '%@' Encoding:%tu MaxLength: %tu Range: %@ "
@"Used: %tu, %tu LeftOver %@, %@)",
count, gpbString, nsString, encoding, maxBufferCount,
NSStringFromRange(range), pbUsedBufferCount, nsUsedBufferCount,
NSStringFromRange(pbLeftOver), NSStringFromRange(nsLeftOver));
XCTAssertEqual(
pbLeftOver.length, nsLeftOver.length,
@"PB %d '%@' vs '%@' Encoding:%tu MaxLength: %tu Range: %@ "
@"Used: %tu, %tu LeftOver %@, %@)",
count, gpbString, nsString, encoding, maxBufferCount,
NSStringFromRange(range), pbUsedBufferCount, nsUsedBufferCount,
NSStringFromRange(pbLeftOver), NSStringFromRange(nsLeftOver));
++count;
}
}
++i;
}
}
- (void)testLengthAndGetBytes {
// This test exists as an attempt to ferret out a bug.
// http://b/13516532
size_t i = 0;
char pbBuffer[256];
char nsBuffer[256];
for (NSString *nsString in nsStrings_) {
GPBString *gpbString = gpbStrings_[i++];
NSUInteger nsLength =
[nsString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSUInteger pbLength =
[gpbString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
XCTAssertEqual(nsLength, pbLength, @"%@ %@", nsString, gpbString);
NSUInteger pbUsedBufferCount = 0;
NSUInteger nsUsedBufferCount = 0;
NSRange pbLeftOver = NSMakeRange(0, 0);
NSRange nsLeftOver = NSMakeRange(0, 0);
NSRange range = NSMakeRange(0, [gpbString length]);
BOOL pbGotBytes = [gpbString getBytes:pbBuffer
maxLength:sizeof(pbBuffer)
usedLength:&pbUsedBufferCount
encoding:NSUTF8StringEncoding
options:0
range:range
remainingRange:&pbLeftOver];
BOOL nsGotBytes = [nsString getBytes:nsBuffer
maxLength:sizeof(nsBuffer)
usedLength:&nsUsedBufferCount
encoding:NSUTF8StringEncoding
options:0
range:range
remainingRange:&nsLeftOver];
XCTAssertTrue(pbGotBytes, @"%@", gpbString);
XCTAssertTrue(nsGotBytes, @"%@", nsString);
XCTAssertEqual(pbUsedBufferCount, pbLength, @"%@", gpbString);
XCTAssertEqual(nsUsedBufferCount, nsLength, @"%@", nsString);
}
}
@end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment