Skip to content

Commit 978ac72

Browse files
committed
[SR-190] Problem Calling CFStringGetBytes With UTF-16 Encoding
Pass the encoding requested for CFStringGetBytes up to the Swift code so it can use either UTF8 or UTF16 view to get the correct bytes.
1 parent cb5887d commit 978ac72

File tree

4 files changed

+66
-12
lines changed

4 files changed

+66
-12
lines changed

CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ struct _NSStringBridge {
9898
CFIndex (*length)(CFTypeRef str);
9999
UniChar (*characterAtIndex)(CFTypeRef str, CFIndex idx);
100100
void (*getCharacters)(CFTypeRef str, CFRange range, UniChar *buffer);
101-
CFIndex (*__getBytes)(CFTypeRef str, CFRange range, uint8_t *buffer, CFIndex maxBufLen, CFIndex *usedBufLen);
101+
CFIndex (*__getBytes)(CFTypeRef str, CFStringEncoding encoding, CFRange range, uint8_t *buffer, CFIndex maxBufLen, CFIndex *usedBufLen);
102102
const char *_Nullable (*_Nonnull _fastCStringContents)(CFTypeRef str);
103103
const UniChar *_Nullable (*_Nonnull _fastCharacterContents)(CFTypeRef str);
104104
bool (*_getCString)(CFTypeRef str, char *buffer, size_t len, UInt32 encoding);

CoreFoundation/String.subproj/CFString.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2041,7 +2041,7 @@ int _CFStringCheckAndGetCharacters(CFStringRef str, CFRange range, UniChar *buff
20412041

20422042
CFIndex CFStringGetBytes(CFStringRef str, CFRange range, CFStringEncoding encoding, uint8_t lossByte, Boolean isExternalRepresentation, uint8_t *buffer, CFIndex maxBufLen, CFIndex *usedBufLen) {
20432043
if (CF_IS_SWIFT(CFStringGetTypeID(), str) && __CFSwiftBridge.NSString.__getBytes != NULL) {
2044-
return __CFSwiftBridge.NSString.__getBytes(str, range, buffer, maxBufLen, usedBufLen);
2044+
return __CFSwiftBridge.NSString.__getBytes(str, encoding, range, buffer, maxBufLen, usedBufLen);
20452045
}
20462046
__CFAssertIsNotNegative(maxBufLen);
20472047

Foundation/NSCFString.swift

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,45 @@ internal func _CFSwiftStringGetCharacters(str: AnyObject, range: CFRange, buffer
113113
(str as! NSString).getCharacters(buffer, range: NSMakeRange(range.location, range.length))
114114
}
115115

116-
internal func _CFSwiftStringGetBytes(str: AnyObject, range: CFRange, buffer: UnsafeMutablePointer<UInt8>, maxBufLen: CFIndex, usedBufLen: UnsafeMutablePointer<CFIndex>) -> CFIndex {
117-
let s = (str as! NSString)._swiftObject.utf8
118-
let start = s.startIndex
119-
if buffer != nil {
120-
for idx in 0..<range.length {
121-
let c = s[start.advancedBy(idx + range.location)]
122-
buffer.advancedBy(idx).initialize(c)
116+
internal func _CFSwiftStringGetBytes(str: AnyObject, encoding: CFStringEncoding, range: CFRange, buffer: UnsafeMutablePointer<UInt8>, maxBufLen: CFIndex, usedBufLen: UnsafeMutablePointer<CFIndex>) -> CFIndex {
117+
switch encoding {
118+
// TODO: Don't treat many encodings like they are UTF8
119+
case CFStringEncoding(kCFStringEncodingUTF8), CFStringEncoding(kCFStringEncodingISOLatin1), CFStringEncoding(kCFStringEncodingMacRoman), CFStringEncoding(kCFStringEncodingASCII), CFStringEncoding(kCFStringEncodingNonLossyASCII):
120+
let encodingView = (str as! NSString)._swiftObject.utf8
121+
let start = encodingView.startIndex
122+
if buffer != nil {
123+
for idx in 0..<range.length {
124+
let character = encodingView[start.advancedBy(idx + range.location)]
125+
buffer.advancedBy(idx).initialize(character)
126+
}
123127
}
128+
if usedBufLen != nil {
129+
usedBufLen.memory = range.length
130+
}
131+
132+
case CFStringEncoding(kCFStringEncodingUTF16):
133+
let encodingView = (str as! NSString)._swiftObject.utf16
134+
let start = encodingView.startIndex
135+
if buffer != nil {
136+
for idx in 0..<range.length {
137+
// Since character is 2 bytes but the buffer is in term of 1 byte values, we have to split it up
138+
let character = encodingView[start.advancedBy(idx + range.location)]
139+
let byte0 = UInt8(character & 0x00ff)
140+
let byte1 = UInt8((character >> 8) & 0x00ff)
141+
buffer.advancedBy(idx * 2).initialize(byte0)
142+
buffer.advancedBy((idx * 2) + 1).initialize(byte1)
143+
}
144+
}
145+
if usedBufLen != nil {
146+
// Every character was 2 bytes
147+
usedBufLen.memory = range.length * 2
148+
}
149+
150+
151+
default:
152+
fatalError("Attempted to get bytes of a Swift string using an unsupported encoding")
124153
}
125-
if usedBufLen != nil {
126-
usedBufLen.memory = range.length
127-
}
154+
128155
return range.length
129156
}
130157

TestFoundation/TestNSString.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class TestNSString : XCTestCase {
4444
("test_longLongValue", test_longLongValue ),
4545
("test_rangeOfCharacterFromSet", test_rangeOfCharacterFromSet ),
4646
("test_CFStringCreateMutableCopy", test_CFStringCreateMutableCopy),
47+
("test_swiftStringUTF16", test_swiftStringUTF16),
4748
]
4849
}
4950

@@ -309,4 +310,30 @@ class TestNSString : XCTestCase {
309310
let str = unsafeBitCast(mCopy, NSString.self).bridge()
310311
XCTAssertEqual(nsstring.bridge(), str)
311312
}
313+
314+
// This test verifies that CFStringGetBytes with a UTF16 encoding works on an NSString backed by a Swift string
315+
func test_swiftStringUTF16() {
316+
#if os(OSX) || os(iOS)
317+
let kCFStringEncodingUTF16 = CFStringBuiltInEncodings.UTF16.rawValue
318+
#endif
319+
320+
let testString = "hello world"
321+
let string = NSString(string: testString)
322+
let cfString = unsafeBitCast(string, CFStringRef.self)
323+
324+
// Get the bytes as UTF16
325+
let reservedLength = 50
326+
var buf : [UInt8] = []
327+
buf.reserveCapacity(reservedLength)
328+
var usedLen : CFIndex = 0
329+
buf.withUnsafeMutableBufferPointer { p in
330+
CFStringGetBytes(cfString, CFRangeMake(0, CFStringGetLength(cfString)), CFStringEncoding(kCFStringEncodingUTF16), 0, false, p.baseAddress, reservedLength, &usedLen)
331+
}
332+
333+
// Make a new string out of it
334+
let newCFString = CFStringCreateWithBytes(nil, buf, usedLen, CFStringEncoding(kCFStringEncodingUTF16), false)
335+
let newString = unsafeBitCast(newCFString, NSString.self)
336+
337+
XCTAssertTrue(newString.isEqualToString(testString))
338+
}
312339
}

0 commit comments

Comments
 (0)