diff --git a/Sources/Foundation/NSConcreteValue.swift b/Sources/Foundation/NSConcreteValue.swift index c28f5b2547..9b121f8000 100644 --- a/Sources/Foundation/NSConcreteValue.swift +++ b/Sources/Foundation/NSConcreteValue.swift @@ -97,6 +97,7 @@ internal class NSConcreteValue : NSValue, @unchecked Sendable { value.copyMemory(from: self._storage, byteCount: self._size) } + @available(*, deprecated, message: "On platforms without Objective-C autorelease pools, use withCString instead") override var objCType : UnsafePointer { return NSString(self._typeInfo.name).utf8String! // XXX leaky } diff --git a/Sources/Foundation/NSSpecialValue.swift b/Sources/Foundation/NSSpecialValue.swift index 3f95cc47eb..055fabdc9d 100644 --- a/Sources/Foundation/NSSpecialValue.swift +++ b/Sources/Foundation/NSSpecialValue.swift @@ -97,6 +97,7 @@ internal class NSSpecialValue : NSValue, @unchecked Sendable { _value.encodeWithCoder(aCoder) } + @available(*, deprecated, message: "On platforms without Objective-C autorelease pools, use withCString instead") override var objCType : UnsafePointer { let typeName = NSSpecialValue._objCTypeFromType(type(of: _value)) return typeName!._bridgeToObjectiveC().utf8String! // leaky diff --git a/Sources/Foundation/NSString.swift b/Sources/Foundation/NSString.swift index 79ec002d5b..88379a9351 100644 --- a/Sources/Foundation/NSString.swift +++ b/Sources/Foundation/NSString.swift @@ -160,32 +160,19 @@ internal func _createRegexForPattern(_ pattern: String, _ options: NSRegularExpr return regex } -internal func _bytesInEncoding(_ str: NSString, _ encoding: String.Encoding, _ fatalOnError: Bool, _ externalRep: Bool, _ lossy: Bool) -> UnsafePointer? { - let theRange = NSRange(location: 0, length: str.length) +// Caller must free, or leak, the pointer +fileprivate func _allocateBytesInEncoding(_ str: NSString, _ encoding: String.Encoding) -> UnsafeMutableBufferPointer? { + let theRange: NSRange = NSRange(location: 0, length: str.length) var cLength = 0 - var used = 0 - var options: NSString.EncodingConversionOptions = [] - if externalRep { - options.formUnion(.externalRepresentation) - } - if lossy { - options.formUnion(.allowLossy) - } - if !str.getBytes(nil, maxLength: Int.max - 1, usedLength: &cLength, encoding: encoding.rawValue, options: options, range: theRange, remaining: nil) { - if fatalOnError { - fatalError("Conversion on encoding failed") - } + if !str.getBytes(nil, maxLength: Int.max - 1, usedLength: &cLength, encoding: encoding.rawValue, options: [], range: theRange, remaining: nil) { return nil } - let buffer = malloc(cLength + 1)!.bindMemory(to: Int8.self, capacity: cLength + 1) - if !str.getBytes(buffer, maxLength: cLength, usedLength: &used, encoding: encoding.rawValue, options: options, range: theRange, remaining: nil) { - fatalError("Internal inconsistency; previously claimed getBytes returned success but failed with similar invocation") - } + let buffer = UnsafeMutableBufferPointer.allocate(capacity: cLength + 1) + buffer.initialize(repeating: 0) + _ = str.getBytes(buffer.baseAddress, maxLength: cLength, usedLength: nil, encoding: encoding.rawValue, options: [], range: theRange, remaining: nil) - buffer.advanced(by: cLength).initialize(to: 0) - - return UnsafePointer(buffer) // leaked and should be autoreleased via a NSData backing but we cannot here + return buffer } internal func isALineSeparatorTypeCharacter(_ ch: unichar) -> Bool { @@ -903,8 +890,13 @@ extension NSString { } } + @available(*, deprecated, message: "On platforms without Objective-C autorelease pools, use withCString instead") public var utf8String: UnsafePointer? { - return _bytesInEncoding(self, .utf8, false, false, false) + guard let buffer = _allocateBytesInEncoding(self, .utf8) else { + return nil + } + // leaked. On Darwin, freed via an autorelease + return UnsafePointer(buffer.baseAddress) } public var fastestEncoding: UInt { @@ -961,8 +953,24 @@ extension NSString { 0, nil, 0, nil) == length } - public func cString(using encoding: UInt) -> UnsafePointer? { - return _bytesInEncoding(self, String.Encoding(rawValue: encoding), false, false, false) + @available(*, deprecated, message: "On platforms without Objective-C autorelease pools, use withCString(encodedAs:_) instead") + public func cString(using encoding: UInt) -> UnsafePointer? { + // leaked. On Darwin, freed via an autorelease + guard let buffer = _allocateBytesInEncoding(self, String.Encoding(rawValue: encoding)) else { + return nil + } + // leaked. On Darwin, freed via an autorelease + return UnsafePointer(buffer.baseAddress) + } + + internal func _withCString(using encoding: UInt, closure: (UnsafePointer?) -> T) -> T { + let buffer = _allocateBytesInEncoding(self, String.Encoding(rawValue: encoding)) + let result = closure(buffer?.baseAddress) + if let buffer { + buffer.deinitialize() + buffer.deallocate() + } + return result } public func getCString(_ buffer: UnsafeMutablePointer, maxLength maxBufferCount: Int, encoding: UInt) -> Bool { diff --git a/Sources/Foundation/NSStringAPI.swift b/Sources/Foundation/NSStringAPI.swift index 26e99c48e8..d725d944e6 100644 --- a/Sources/Foundation/NSStringAPI.swift +++ b/Sources/Foundation/NSStringAPI.swift @@ -633,9 +633,8 @@ extension StringProtocol { /// Returns a representation of the string as a C string /// using a given encoding. public func cString(using encoding: String.Encoding) -> [CChar]? { - return withExtendedLifetime(_ns) { - (s: NSString) -> [CChar]? in - _persistCString(s.cString(using: encoding.rawValue)) + return _ns._withCString(using: encoding.rawValue) { + _persistCString($0) } } diff --git a/Tests/Foundation/TestNSString.swift b/Tests/Foundation/TestNSString.swift index b3e24278a3..73d80bd119 100644 --- a/Tests/Foundation/TestNSString.swift +++ b/Tests/Foundation/TestNSString.swift @@ -316,6 +316,12 @@ class TestNSString: LoopbackServerTest { XCTAssertNil(string) } + func test_cStringArray() { + let str = "abc" + let encoded = str.cString(using: .ascii) + XCTAssertEqual(encoded, [97, 98, 99, 0]) + } + func test_FromContentsOfURL() throws { throw XCTSkip("Test is flaky in CI: https://bugs.swift.org/browse/SR-10514") #if false