Skip to content

Commit cb4ee87

Browse files
authored
Deprecate cString on non-Darwin (#3885) (#5190)
* Deprecate cString on non-Darwin (#3885) * Address review feedback * Undeprecate one version of the API, which can be implemented correctly
1 parent 15022ff commit cb4ee87

File tree

5 files changed

+42
-27
lines changed

5 files changed

+42
-27
lines changed

Sources/Foundation/NSConcreteValue.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ internal class NSConcreteValue : NSValue, @unchecked Sendable {
9797
value.copyMemory(from: self._storage, byteCount: self._size)
9898
}
9999

100+
@available(*, deprecated, message: "On platforms without Objective-C autorelease pools, use withCString instead")
100101
override var objCType : UnsafePointer<Int8> {
101102
return NSString(self._typeInfo.name).utf8String! // XXX leaky
102103
}

Sources/Foundation/NSSpecialValue.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ internal class NSSpecialValue : NSValue, @unchecked Sendable {
9797
_value.encodeWithCoder(aCoder)
9898
}
9999

100+
@available(*, deprecated, message: "On platforms without Objective-C autorelease pools, use withCString instead")
100101
override var objCType : UnsafePointer<Int8> {
101102
let typeName = NSSpecialValue._objCTypeFromType(type(of: _value))
102103
return typeName!._bridgeToObjectiveC().utf8String! // leaky

Sources/Foundation/NSString.swift

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -160,32 +160,19 @@ internal func _createRegexForPattern(_ pattern: String, _ options: NSRegularExpr
160160
return regex
161161
}
162162

163-
internal func _bytesInEncoding(_ str: NSString, _ encoding: String.Encoding, _ fatalOnError: Bool, _ externalRep: Bool, _ lossy: Bool) -> UnsafePointer<Int8>? {
164-
let theRange = NSRange(location: 0, length: str.length)
163+
// Caller must free, or leak, the pointer
164+
fileprivate func _allocateBytesInEncoding(_ str: NSString, _ encoding: String.Encoding) -> UnsafeMutableBufferPointer<Int8>? {
165+
let theRange: NSRange = NSRange(location: 0, length: str.length)
165166
var cLength = 0
166-
var used = 0
167-
var options: NSString.EncodingConversionOptions = []
168-
if externalRep {
169-
options.formUnion(.externalRepresentation)
170-
}
171-
if lossy {
172-
options.formUnion(.allowLossy)
173-
}
174-
if !str.getBytes(nil, maxLength: Int.max - 1, usedLength: &cLength, encoding: encoding.rawValue, options: options, range: theRange, remaining: nil) {
175-
if fatalOnError {
176-
fatalError("Conversion on encoding failed")
177-
}
167+
if !str.getBytes(nil, maxLength: Int.max - 1, usedLength: &cLength, encoding: encoding.rawValue, options: [], range: theRange, remaining: nil) {
178168
return nil
179169
}
180170

181-
let buffer = malloc(cLength + 1)!.bindMemory(to: Int8.self, capacity: cLength + 1)
182-
if !str.getBytes(buffer, maxLength: cLength, usedLength: &used, encoding: encoding.rawValue, options: options, range: theRange, remaining: nil) {
183-
fatalError("Internal inconsistency; previously claimed getBytes returned success but failed with similar invocation")
184-
}
171+
let buffer = UnsafeMutableBufferPointer<Int8>.allocate(capacity: cLength + 1)
172+
buffer.initialize(repeating: 0)
173+
_ = str.getBytes(buffer.baseAddress, maxLength: cLength, usedLength: nil, encoding: encoding.rawValue, options: [], range: theRange, remaining: nil)
185174

186-
buffer.advanced(by: cLength).initialize(to: 0)
187-
188-
return UnsafePointer(buffer) // leaked and should be autoreleased via a NSData backing but we cannot here
175+
return buffer
189176
}
190177

191178
internal func isALineSeparatorTypeCharacter(_ ch: unichar) -> Bool {
@@ -903,8 +890,13 @@ extension NSString {
903890
}
904891
}
905892

893+
@available(*, deprecated, message: "On platforms without Objective-C autorelease pools, use withCString instead")
906894
public var utf8String: UnsafePointer<Int8>? {
907-
return _bytesInEncoding(self, .utf8, false, false, false)
895+
guard let buffer = _allocateBytesInEncoding(self, .utf8) else {
896+
return nil
897+
}
898+
// leaked. On Darwin, freed via an autorelease
899+
return UnsafePointer<Int8>(buffer.baseAddress)
908900
}
909901

910902
public var fastestEncoding: UInt {
@@ -961,8 +953,24 @@ extension NSString {
961953
0, nil, 0, nil) == length
962954
}
963955

964-
public func cString(using encoding: UInt) -> UnsafePointer<Int8>? {
965-
return _bytesInEncoding(self, String.Encoding(rawValue: encoding), false, false, false)
956+
@available(*, deprecated, message: "On platforms without Objective-C autorelease pools, use withCString(encodedAs:_) instead")
957+
public func cString(using encoding: UInt) -> UnsafePointer<Int8>? {
958+
// leaked. On Darwin, freed via an autorelease
959+
guard let buffer = _allocateBytesInEncoding(self, String.Encoding(rawValue: encoding)) else {
960+
return nil
961+
}
962+
// leaked. On Darwin, freed via an autorelease
963+
return UnsafePointer<Int8>(buffer.baseAddress)
964+
}
965+
966+
internal func _withCString<T>(using encoding: UInt, closure: (UnsafePointer<Int8>?) -> T) -> T {
967+
let buffer = _allocateBytesInEncoding(self, String.Encoding(rawValue: encoding))
968+
let result = closure(buffer?.baseAddress)
969+
if let buffer {
970+
buffer.deinitialize()
971+
buffer.deallocate()
972+
}
973+
return result
966974
}
967975

968976
public func getCString(_ buffer: UnsafeMutablePointer<Int8>, maxLength maxBufferCount: Int, encoding: UInt) -> Bool {

Sources/Foundation/NSStringAPI.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -633,9 +633,8 @@ extension StringProtocol {
633633
/// Returns a representation of the string as a C string
634634
/// using a given encoding.
635635
public func cString(using encoding: String.Encoding) -> [CChar]? {
636-
return withExtendedLifetime(_ns) {
637-
(s: NSString) -> [CChar]? in
638-
_persistCString(s.cString(using: encoding.rawValue))
636+
return _ns._withCString(using: encoding.rawValue) {
637+
_persistCString($0)
639638
}
640639
}
641640

Tests/Foundation/TestNSString.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,12 @@ class TestNSString: LoopbackServerTest {
316316
XCTAssertNil(string)
317317
}
318318

319+
func test_cStringArray() {
320+
let str = "abc"
321+
let encoded = str.cString(using: .ascii)
322+
XCTAssertEqual(encoded, [97, 98, 99, 0])
323+
}
324+
319325
func test_FromContentsOfURL() throws {
320326
throw XCTSkip("Test is flaky in CI: https://bugs.swift.org/browse/SR-10514")
321327
#if false

0 commit comments

Comments
 (0)