|
@@ -193,6 +193,32 @@
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+- (void)assertWriteStringNoTag:(NSData*)data
|
|
|
|
+ value:(NSString *)value
|
|
|
|
+ context:(NSString *)contextMessage {
|
|
|
|
+ NSOutputStream* rawOutput = [NSOutputStream outputStreamToMemory];
|
|
|
|
+ GPBCodedOutputStream* output =
|
|
|
|
+ [GPBCodedOutputStream streamWithOutputStream:rawOutput];
|
|
|
|
+ [output writeStringNoTag:value];
|
|
|
|
+ [output flush];
|
|
|
|
+
|
|
|
|
+ NSData* actual =
|
|
|
|
+ [rawOutput propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
|
|
|
|
+ XCTAssertEqualObjects(data, actual, @"%@", contextMessage);
|
|
|
|
+
|
|
|
|
+ // Try different block sizes.
|
|
|
|
+ for (int blockSize = 1; blockSize <= 16; blockSize *= 2) {
|
|
|
|
+ rawOutput = [NSOutputStream outputStreamToMemory];
|
|
|
|
+ output = [GPBCodedOutputStream streamWithOutputStream:rawOutput
|
|
|
|
+ bufferSize:blockSize];
|
|
|
|
+ [output writeStringNoTag:value];
|
|
|
|
+ [output flush];
|
|
|
|
+
|
|
|
|
+ actual = [rawOutput propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
|
|
|
|
+ XCTAssertEqualObjects(data, actual, @"%@", contextMessage);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
- (void)testWriteVarint1 {
|
|
- (void)testWriteVarint1 {
|
|
[self assertWriteVarint:bytes(0x00) value:0];
|
|
[self assertWriteVarint:bytes(0x00) value:0];
|
|
}
|
|
}
|
|
@@ -337,4 +363,64 @@
|
|
XCTAssertEqualObjects(rawBytes, goldenData);
|
|
XCTAssertEqualObjects(rawBytes, goldenData);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+- (void)testCFStringGetCStringPtrAndStringsWithNullChars {
|
|
|
|
+ // This test exists to verify that CFStrings with embedded NULLs still expose
|
|
|
|
+ // their raw buffer if they are backed by UTF8 storage. If this fails, the
|
|
|
|
+ // quick/direct access paths in GPBCodedOutputStream that depend on
|
|
|
|
+ // CFStringGetCStringPtr need to be re-evalutated (maybe just removed).
|
|
|
|
+ // And yes, we do get NULLs in strings from some servers.
|
|
|
|
+
|
|
|
|
+ char zeroTest[] = "\0Test\0String";
|
|
|
|
+ // Note: there is a \0 at the end of this since it is a c-string.
|
|
|
|
+ NSString *asNSString = [[NSString alloc] initWithBytes:zeroTest
|
|
|
|
+ length:sizeof(zeroTest)
|
|
|
|
+ encoding:NSUTF8StringEncoding];
|
|
|
|
+ const char *cString =
|
|
|
|
+ CFStringGetCStringPtr((CFStringRef)asNSString, kCFStringEncodingUTF8);
|
|
|
|
+ XCTAssertTrue(cString != NULL);
|
|
|
|
+ // Again, if the above assert fails, then it means NSString no longer exposes
|
|
|
|
+ // the raw utf8 storage of a string created from utf8 input, so the code using
|
|
|
|
+ // CFStringGetCStringPtr in GPBCodedOutputStream will still work (it will take
|
|
|
|
+ // a different code path); but the optimizations for when
|
|
|
|
+ // CFStringGetCStringPtr does work could possibly go away.
|
|
|
|
+
|
|
|
|
+ XCTAssertEqual(sizeof(zeroTest),
|
|
|
|
+ [asNSString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
|
|
|
|
+ XCTAssertTrue(0 == memcmp(cString, zeroTest, sizeof(zeroTest)));
|
|
|
|
+ [asNSString release];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+- (void)testWriteStringsWithZeroChar {
|
|
|
|
+ // Unicode allows `\0` as a character, and NSString is a class cluster, so
|
|
|
|
+ // there are a few different classes that could end up beind a given string.
|
|
|
|
+ // Historically, we've seen differences based on constant strings in code and
|
|
|
|
+ // strings built via the NSString apis. So this round trips them to ensure
|
|
|
|
+ // they are acting as expected.
|
|
|
|
+
|
|
|
|
+ NSArray<NSString *> *strs = @[
|
|
|
|
+ @"\0at start",
|
|
|
|
+ @"in\0middle",
|
|
|
|
+ @"at end\0",
|
|
|
|
+ ];
|
|
|
|
+ int i = 0;
|
|
|
|
+ for (NSString *str in strs) {
|
|
|
|
+ NSData *asUTF8 = [str dataUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
+ NSMutableData *expected = [NSMutableData data];
|
|
|
|
+ uint8_t lengthByte = (uint8_t)asUTF8.length;
|
|
|
|
+ [expected appendBytes:&lengthByte length:1];
|
|
|
|
+ [expected appendData:asUTF8];
|
|
|
|
+
|
|
|
|
+ NSString *context = [NSString stringWithFormat:@"Loop %d - Literal", i];
|
|
|
|
+ [self assertWriteStringNoTag:expected value:str context:context];
|
|
|
|
+
|
|
|
|
+ // Force a new string to be built which gets a different class from the
|
|
|
|
+ // NSString class cluster than the literal did.
|
|
|
|
+ NSString *str2 = [NSString stringWithFormat:@"%@", str];
|
|
|
|
+ context = [NSString stringWithFormat:@"Loop %d - Built", i];
|
|
|
|
+ [self assertWriteStringNoTag:expected value:str2 context:context];
|
|
|
|
+
|
|
|
|
+ ++i;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
@end
|
|
@end
|