Skip to content

Commit 1168239

Browse files
authored
Merge pull request swiftlang#2994 from Lukasa/cb-lifetime-management-in-nsstring
Cleanup lifetime management around NSData.bytes.
2 parents 2232811 + a8d300d commit 1168239

File tree

11 files changed

+196
-146
lines changed

11 files changed

+196
-146
lines changed

Sources/Foundation/Data.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2020,7 +2020,9 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
20202020
@inlinable // This is @inlinable as a convenience initializer.
20212021
public init(contentsOf url: __shared URL, options: Data.ReadingOptions = []) throws {
20222022
let d = try NSData(contentsOf: url, options: ReadingOptions(rawValue: options.rawValue))
2023-
self.init(bytes: d.bytes, count: d.length)
2023+
self = withExtendedLifetime(d) {
2024+
return Data(bytes: d.bytes, count: d.length)
2025+
}
20242026
}
20252027

20262028
/// Initialize a `Data` from a Base-64 encoded String using the given options.
@@ -2031,7 +2033,9 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
20312033
@inlinable // This is @inlinable as a convenience initializer.
20322034
public init?(base64Encoded base64String: __shared String, options: Data.Base64DecodingOptions = []) {
20332035
if let d = NSData(base64Encoded: base64String, options: Base64DecodingOptions(rawValue: options.rawValue)) {
2034-
self.init(bytes: d.bytes, count: d.length)
2036+
self = withExtendedLifetime(d) {
2037+
Data(bytes: d.bytes, count: d.length)
2038+
}
20352039
} else {
20362040
return nil
20372041
}
@@ -2046,7 +2050,9 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
20462050
@inlinable // This is @inlinable as a convenience initializer.
20472051
public init?(base64Encoded base64Data: __shared Data, options: Data.Base64DecodingOptions = []) {
20482052
if let d = NSData(base64Encoded: base64Data, options: Base64DecodingOptions(rawValue: options.rawValue)) {
2049-
self.init(bytes: d.bytes, count: d.length)
2053+
self = withExtendedLifetime(d) {
2054+
Data(bytes: d.bytes, count: d.length)
2055+
}
20502056
} else {
20512057
return nil
20522058
}

Sources/Foundation/NSAttributedString.swift

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -558,30 +558,31 @@ internal func _NSReadIntFromMutableAttributedStringCoding(_ data: NSData, _ star
558558
let length = data.length
559559

560560
var value = 0
561-
562-
while offset < length {
563-
let i = Int(data.bytes.load(fromByteOffset: offset, as: UInt8.self))
564-
565-
offset += 1
566-
567-
let isLast = i < 128
568-
569-
let intermediateValue = multiplier.multipliedReportingOverflow(by: isLast ? i : (i - 128))
570-
guard !intermediateValue.overflow else { return nil }
571-
572-
let newValue = value.addingReportingOverflow(intermediateValue.partialValue)
573-
guard !newValue.overflow else { return nil }
574-
575-
value = newValue.partialValue
576561

577-
if isLast {
578-
return (value: value, newOffset: offset)
562+
return withExtendedLifetime(data) { _ -> (Int, Int)? in
563+
while offset < length {
564+
let i = Int(data.bytes.load(fromByteOffset: offset, as: UInt8.self))
565+
566+
offset += 1
567+
568+
let isLast = i < 128
569+
570+
let intermediateValue = multiplier.multipliedReportingOverflow(by: isLast ? i : (i - 128))
571+
guard !intermediateValue.overflow else { return nil }
572+
573+
let newValue = value.addingReportingOverflow(intermediateValue.partialValue)
574+
guard !newValue.overflow else { return nil }
575+
576+
value = newValue.partialValue
577+
578+
if isLast {
579+
return (value: value, newOffset: offset)
580+
}
581+
582+
multiplier *= 128
579583
}
580-
581-
multiplier *= 128
584+
return nil // Getting to the end of the stream indicates error, since we were still expecting more bytes
582585
}
583-
584-
return nil // Getting to the end of the stream indicates error, since we were still expecting more bytes
585586
}
586587

587588
internal func _NSWriteIntToMutableAttributedStringCoding(_ i: Int, _ data: NSMutableData) {

Sources/Foundation/NSData.swift

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,20 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
153153
public init(contentsOfFile path: String, options readOptionsMask: ReadingOptions = []) throws {
154154
super.init()
155155
let readResult = try NSData.readBytesFromFileWithExtendedAttributes(path, options: readOptionsMask)
156-
_init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
156+
157+
withExtendedLifetime(readResult) {
158+
_init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
159+
}
157160
}
158161

159162
/// Initializes a data object with the contents of the file at a given path.
160163
public init?(contentsOfFile path: String) {
161164
do {
162165
super.init()
163166
let readResult = try NSData.readBytesFromFileWithExtendedAttributes(path, options: [])
164-
_init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
167+
withExtendedLifetime(readResult) {
168+
_init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
169+
}
165170
} catch {
166171
return nil
167172
}
@@ -179,15 +184,19 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
179184
public init(contentsOf url: URL, options readOptionsMask: ReadingOptions = []) throws {
180185
super.init()
181186
let (data, _) = try NSData.contentsOf(url: url, options: readOptionsMask)
182-
_init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true)
187+
withExtendedLifetime(data) {
188+
_init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true)
189+
}
183190
}
184191

185192
/// Initializes a data object with the data from the location specified by a given URL.
186193
public init?(contentsOf url: URL) {
187194
super.init()
188195
do {
189196
let (data, _) = try NSData.contentsOf(url: url)
190-
_init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true)
197+
withExtendedLifetime(data) {
198+
_init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true)
199+
}
191200
} catch {
192201
return nil
193202
}
@@ -375,15 +384,19 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
375384
guard let data = aDecoder._decodePropertyListForKey("NS.data") as? NSData else {
376385
return nil
377386
}
378-
_init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true)
387+
withExtendedLifetime(data) {
388+
_init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true)
389+
}
379390
} else {
380391
let result : Data? = aDecoder.withDecodedUnsafeBufferPointer(forKey: "NS.bytes") {
381392
guard let buffer = $0 else { return nil }
382393
return Data(buffer: buffer)
383394
}
384395

385-
guard let r = result else { return nil }
386-
_init(bytes: UnsafeMutableRawPointer(mutating: r._nsObject.bytes), length: r.count, copy: true)
396+
guard var r = result else { return nil }
397+
r.withUnsafeMutableBytes {
398+
_init(bytes: $0.baseAddress, length: $0.count, copy: true)
399+
}
387400
}
388401
}
389402

@@ -559,24 +572,27 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
559572
/// Finds and returns the range of the first occurrence of the given data, within the given range, subject to given options.
560573
open func range(of dataToFind: Data, options mask: SearchOptions = [], in searchRange: NSRange) -> NSRange {
561574
let dataToFind = dataToFind._nsObject
562-
guard dataToFind.length > 0 else {return NSRange(location: NSNotFound, length: 0)}
563-
guard let searchRange = Range(searchRange) else {fatalError("invalid range")}
564-
565-
precondition(searchRange.upperBound <= self.length, "range outside the bounds of data")
566575

567-
let basePtr = self.bytes.bindMemory(to: UInt8.self, capacity: self.length)
568-
let baseData = UnsafeBufferPointer<UInt8>(start: basePtr, count: self.length)[searchRange]
569-
let searchPtr = dataToFind.bytes.bindMemory(to: UInt8.self, capacity: dataToFind.length)
570-
let search = UnsafeBufferPointer<UInt8>(start: searchPtr, count: dataToFind.length)
571-
572-
let location : Int?
573-
let anchored = mask.contains(.anchored)
574-
if mask.contains(.backwards) {
575-
location = NSData.searchSubSequence(search.reversed(), inSequence: baseData.reversed(),anchored : anchored).map {$0.base-search.count}
576-
} else {
577-
location = NSData.searchSubSequence(search, inSequence: baseData,anchored : anchored)
576+
return withExtendedLifetime(dataToFind) {
577+
guard dataToFind.length > 0 else {return NSRange(location: NSNotFound, length: 0)}
578+
guard let searchRange = Range(searchRange) else {fatalError("invalid range")}
579+
580+
precondition(searchRange.upperBound <= self.length, "range outside the bounds of data")
581+
582+
let basePtr = self.bytes.bindMemory(to: UInt8.self, capacity: self.length)
583+
let baseData = UnsafeBufferPointer<UInt8>(start: basePtr, count: self.length)[searchRange]
584+
let searchPtr = dataToFind.bytes.bindMemory(to: UInt8.self, capacity: dataToFind.length)
585+
let search = UnsafeBufferPointer<UInt8>(start: searchPtr, count: dataToFind.length)
586+
587+
let location : Int?
588+
let anchored = mask.contains(.anchored)
589+
if mask.contains(.backwards) {
590+
location = NSData.searchSubSequence(search.reversed(), inSequence: baseData.reversed(),anchored : anchored).map {$0.base-search.count}
591+
} else {
592+
location = NSData.searchSubSequence(search, inSequence: baseData,anchored : anchored)
593+
}
594+
return location.map {NSRange(location: $0, length: search.count)} ?? NSRange(location: NSNotFound, length: 0)
578595
}
579-
return location.map {NSRange(location: $0, length: search.count)} ?? NSRange(location: NSNotFound, length: 0)
580596
}
581597

582598
private static func searchSubSequence<T : Collection, T2 : Sequence>(_ subSequence : T2, inSequence seq: T,anchored : Bool) -> T.Index? where T.Iterator.Element : Equatable, T.Iterator.Element == T2.Iterator.Element {

Sources/Foundation/NSString.swift

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,12 +1368,16 @@ extension NSString {
13681368
public convenience init(contentsOf url: URL, encoding enc: UInt) throws {
13691369
let readResult = try NSData(contentsOf: url, options: [])
13701370

1371-
let bytePtr = readResult.bytes.bindMemory(to: UInt8.self, capacity: readResult.length)
1372-
guard let cf = CFStringCreateWithBytes(kCFAllocatorDefault, bytePtr, readResult.length, CFStringConvertNSStringEncodingToEncoding(numericCast(enc)), true) else {
1373-
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInapplicableStringEncoding.rawValue, userInfo: [
1374-
NSDebugDescriptionErrorKey : "Unable to create a string using the specified encoding."
1375-
])
1371+
let cf = try withExtendedLifetime(readResult) { _ -> CFString in
1372+
let bytePtr = readResult.bytes.bindMemory(to: UInt8.self, capacity: readResult.length)
1373+
guard let cf = CFStringCreateWithBytes(kCFAllocatorDefault, bytePtr, readResult.length, CFStringConvertNSStringEncodingToEncoding(numericCast(enc)), true) else {
1374+
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInapplicableStringEncoding.rawValue, userInfo: [
1375+
NSDebugDescriptionErrorKey : "Unable to create a string using the specified encoding."
1376+
])
1377+
}
1378+
return cf
13761379
}
1380+
13771381
var str: String?
13781382
if String._conditionallyBridgeFromObjectiveC(cf._nsObject, result: &str) {
13791383
self.init(str!)
@@ -1391,44 +1395,49 @@ extension NSString {
13911395
public convenience init(contentsOf url: URL, usedEncoding enc: UnsafeMutablePointer<UInt>?) throws {
13921396
let (readResult, textEncodingNameMaybe) = try NSData.contentsOf(url: url)
13931397

1394-
let encoding: UInt
1395-
let offset: Int
1396-
// Look for a BOM (Byte Order Marker) to try and determine the text Encoding, this also skips
1397-
// over the bytes. This takes precedence over the textEncoding in the http header
1398-
let bytePtr = readResult.bytes.bindMemory(to: UInt8.self, capacity:readResult.length)
1399-
if readResult.length >= 4 && bytePtr[0] == 0xFF && bytePtr[1] == 0xFE && bytePtr[2] == 0x00 && bytePtr[3] == 0x00 {
1400-
encoding = String.Encoding.utf32LittleEndian.rawValue
1401-
offset = 4
1402-
}
1403-
else if readResult.length >= 2 && bytePtr[0] == 0xFE && bytePtr[1] == 0xFF {
1404-
encoding = String.Encoding.utf16BigEndian.rawValue
1405-
offset = 2
1406-
}
1407-
else if readResult.length >= 2 && bytePtr[0] == 0xFF && bytePtr[1] == 0xFE {
1408-
encoding = String.Encoding.utf16LittleEndian.rawValue
1409-
offset = 2
1410-
}
1411-
else if readResult.length >= 4 && bytePtr[0] == 0x00 && bytePtr[1] == 0x00 && bytePtr[2] == 0xFE && bytePtr[3] == 0xFF {
1412-
encoding = String.Encoding.utf32BigEndian.rawValue
1413-
offset = 4
1414-
}
1415-
else if let charSet = textEncodingNameMaybe, let textEncoding = String.Encoding(charSet: charSet) {
1416-
encoding = textEncoding.rawValue
1417-
offset = 0
1418-
} else {
1419-
//Need to work on more conditions. This should be the default
1420-
encoding = String.Encoding.utf8.rawValue
1421-
offset = 0
1422-
}
14231398

1424-
// Since the encoding being passed includes the byte order the BOM wont be checked or skipped, so pass offset to
1425-
// manually skip the BOM header.
1426-
guard let cf = CFStringCreateWithBytes(kCFAllocatorDefault, bytePtr + offset, readResult.length - offset,
1427-
CFStringConvertNSStringEncodingToEncoding(numericCast(encoding)), true) else {
1428-
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInapplicableStringEncoding.rawValue, userInfo: [
1429-
NSDebugDescriptionErrorKey : "Unable to create a string using the specified encoding."
1430-
])
1399+
let (cf, encoding) = try withExtendedLifetime(readResult) { _ -> (CFString, UInt) in
1400+
let encoding: UInt
1401+
let offset: Int
1402+
// Look for a BOM (Byte Order Marker) to try and determine the text Encoding, this also skips
1403+
// over the bytes. This takes precedence over the textEncoding in the http header
1404+
let bytePtr = readResult.bytes.bindMemory(to: UInt8.self, capacity:readResult.length)
1405+
if readResult.length >= 4 && bytePtr[0] == 0xFF && bytePtr[1] == 0xFE && bytePtr[2] == 0x00 && bytePtr[3] == 0x00 {
1406+
encoding = String.Encoding.utf32LittleEndian.rawValue
1407+
offset = 4
1408+
}
1409+
else if readResult.length >= 2 && bytePtr[0] == 0xFE && bytePtr[1] == 0xFF {
1410+
encoding = String.Encoding.utf16BigEndian.rawValue
1411+
offset = 2
1412+
}
1413+
else if readResult.length >= 2 && bytePtr[0] == 0xFF && bytePtr[1] == 0xFE {
1414+
encoding = String.Encoding.utf16LittleEndian.rawValue
1415+
offset = 2
1416+
}
1417+
else if readResult.length >= 4 && bytePtr[0] == 0x00 && bytePtr[1] == 0x00 && bytePtr[2] == 0xFE && bytePtr[3] == 0xFF {
1418+
encoding = String.Encoding.utf32BigEndian.rawValue
1419+
offset = 4
1420+
}
1421+
else if let charSet = textEncodingNameMaybe, let textEncoding = String.Encoding(charSet: charSet) {
1422+
encoding = textEncoding.rawValue
1423+
offset = 0
1424+
} else {
1425+
//Need to work on more conditions. This should be the default
1426+
encoding = String.Encoding.utf8.rawValue
1427+
offset = 0
1428+
}
1429+
1430+
// Since the encoding being passed includes the byte order the BOM wont be checked or skipped, so pass offset to
1431+
// manually skip the BOM header.
1432+
guard let cf = CFStringCreateWithBytes(kCFAllocatorDefault, bytePtr + offset, readResult.length - offset,
1433+
CFStringConvertNSStringEncodingToEncoding(numericCast(encoding)), true) else {
1434+
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInapplicableStringEncoding.rawValue, userInfo: [
1435+
NSDebugDescriptionErrorKey : "Unable to create a string using the specified encoding."
1436+
])
1437+
}
1438+
return (cf, encoding)
14311439
}
1440+
14321441
var str: String?
14331442
if String._conditionallyBridgeFromObjectiveC(cf._nsObject, result: &str) {
14341443
self.init(str!)

Sources/Foundation/NSTimeZone.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ open class NSTimeZone : NSObject, NSCopying, NSSecureCoding, NSCoding {
9595

9696
public convenience init?(abbreviation: String) {
9797
let abbr = abbreviation._cfObject
98-
guard let name = unsafeBitCast(CFDictionaryGetValue(CFTimeZoneCopyAbbreviationDictionary(), unsafeBitCast(abbr, to: UnsafeRawPointer.self)), to: NSString?.self) else {
98+
let possibleName: NSString? = withExtendedLifetime(abbr) {
99+
return unsafeBitCast(CFDictionaryGetValue(CFTimeZoneCopyAbbreviationDictionary(), unsafeBitCast(abbr, to: UnsafeRawPointer.self)), to: NSString?.self)
100+
}
101+
guard let name = possibleName else {
99102
return nil
100103
}
101104
self.init(name: name._swiftObject , data: nil)

Sources/FoundationNetworking/NSURLRequest.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,10 @@ open class NSURLRequest : NSObject, NSSecureCoding, NSCopying, NSMutableCopying
237237
aCoder.encode(self.cachePolicy.rawValue._bridgeToObjectiveC(), forKey: "NS._cachePolicy")
238238
aCoder.encode(self.timeoutInterval._bridgeToObjectiveC(), forKey: "NS._timeoutInterval")
239239
if let httpBody = self.httpBody?._bridgeToObjectiveC() {
240-
let bytePtr = httpBody.bytes.bindMemory(to: UInt8.self, capacity: httpBody.length)
241-
aCoder.encodeBytes(bytePtr, length: httpBody.length, forKey: "NS.httpBody")
240+
withExtendedLifetime(httpBody) {
241+
let bytePtr = httpBody.bytes.bindMemory(to: UInt8.self, capacity: httpBody.length)
242+
aCoder.encodeBytes(bytePtr, length: httpBody.length, forKey: "NS.httpBody")
243+
}
242244
}
243245
//On macOS input stream is not encoded.
244246
aCoder.encode(self.networkServiceType.rawValue._bridgeToObjectiveC(), forKey: "NS._networkServiceType")

Sources/FoundationNetworking/URLSession/NativeProtocol.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
256256
if case .inMemory(let bodyData) = bodyDataDrain {
257257
var data = Data()
258258
if let body = bodyData {
259-
data = Data(bytes: body.bytes, count: body.length)
259+
withExtendedLifetime(body) {
260+
data = Data(bytes: body.bytes, count: body.length)
261+
}
260262
}
261263
self.client?.urlProtocol(self, didLoad: data)
262264
self.internalState = .taskCompleted

Sources/FoundationNetworking/URLSession/NetworkingSpecific.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class _NSNonfileURLContentLoader: _NSNonfileURLContentLoading {
8585
switch statusCode {
8686
// These are the only valid response codes that data will be returned for, all other codes will be treated as error.
8787
case 101, 200...399, 401, 407:
88-
return (NSData(bytes: UnsafeMutableRawPointer(mutating: (data as NSData).bytes), length: data.count), urlResponse?.textEncodingName)
88+
return (data as NSData, urlResponse?.textEncodingName)
8989

9090
default:
9191
break

0 commit comments

Comments
 (0)