diff --git a/Sources/Foundation/Data.swift b/Sources/Foundation/Data.swift index 3ce87d24a6..1d21b6067e 100644 --- a/Sources/Foundation/Data.swift +++ b/Sources/Foundation/Data.swift @@ -2020,7 +2020,9 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl @inlinable // This is @inlinable as a convenience initializer. public init(contentsOf url: __shared URL, options: Data.ReadingOptions = []) throws { let d = try NSData(contentsOf: url, options: ReadingOptions(rawValue: options.rawValue)) - self.init(bytes: d.bytes, count: d.length) + self = withExtendedLifetime(d) { + return Data(bytes: d.bytes, count: d.length) + } } /// Initialize a `Data` from a Base-64 encoded String using the given options. @@ -2031,7 +2033,9 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl @inlinable // This is @inlinable as a convenience initializer. public init?(base64Encoded base64String: __shared String, options: Data.Base64DecodingOptions = []) { if let d = NSData(base64Encoded: base64String, options: Base64DecodingOptions(rawValue: options.rawValue)) { - self.init(bytes: d.bytes, count: d.length) + self = withExtendedLifetime(d) { + Data(bytes: d.bytes, count: d.length) + } } else { return nil } @@ -2046,7 +2050,9 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl @inlinable // This is @inlinable as a convenience initializer. public init?(base64Encoded base64Data: __shared Data, options: Data.Base64DecodingOptions = []) { if let d = NSData(base64Encoded: base64Data, options: Base64DecodingOptions(rawValue: options.rawValue)) { - self.init(bytes: d.bytes, count: d.length) + self = withExtendedLifetime(d) { + Data(bytes: d.bytes, count: d.length) + } } else { return nil } diff --git a/Sources/Foundation/NSAttributedString.swift b/Sources/Foundation/NSAttributedString.swift index 4603407396..4f7523f179 100644 --- a/Sources/Foundation/NSAttributedString.swift +++ b/Sources/Foundation/NSAttributedString.swift @@ -558,30 +558,31 @@ internal func _NSReadIntFromMutableAttributedStringCoding(_ data: NSData, _ star let length = data.length var value = 0 - - while offset < length { - let i = Int(data.bytes.load(fromByteOffset: offset, as: UInt8.self)) - - offset += 1 - - let isLast = i < 128 - - let intermediateValue = multiplier.multipliedReportingOverflow(by: isLast ? i : (i - 128)) - guard !intermediateValue.overflow else { return nil } - - let newValue = value.addingReportingOverflow(intermediateValue.partialValue) - guard !newValue.overflow else { return nil } - - value = newValue.partialValue - if isLast { - return (value: value, newOffset: offset) + return withExtendedLifetime(data) { _ -> (Int, Int)? in + while offset < length { + let i = Int(data.bytes.load(fromByteOffset: offset, as: UInt8.self)) + + offset += 1 + + let isLast = i < 128 + + let intermediateValue = multiplier.multipliedReportingOverflow(by: isLast ? i : (i - 128)) + guard !intermediateValue.overflow else { return nil } + + let newValue = value.addingReportingOverflow(intermediateValue.partialValue) + guard !newValue.overflow else { return nil } + + value = newValue.partialValue + + if isLast { + return (value: value, newOffset: offset) + } + + multiplier *= 128 } - - multiplier *= 128 + return nil // Getting to the end of the stream indicates error, since we were still expecting more bytes } - - return nil // Getting to the end of the stream indicates error, since we were still expecting more bytes } internal func _NSWriteIntToMutableAttributedStringCoding(_ i: Int, _ data: NSMutableData) { diff --git a/Sources/Foundation/NSData.swift b/Sources/Foundation/NSData.swift index eb8e6ac4f5..198941c169 100644 --- a/Sources/Foundation/NSData.swift +++ b/Sources/Foundation/NSData.swift @@ -153,7 +153,10 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { public init(contentsOfFile path: String, options readOptionsMask: ReadingOptions = []) throws { super.init() let readResult = try NSData.readBytesFromFileWithExtendedAttributes(path, options: readOptionsMask) - _init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator) + + withExtendedLifetime(readResult) { + _init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator) + } } /// Initializes a data object with the contents of the file at a given path. @@ -161,7 +164,9 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { do { super.init() let readResult = try NSData.readBytesFromFileWithExtendedAttributes(path, options: []) - _init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator) + withExtendedLifetime(readResult) { + _init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator) + } } catch { return nil } @@ -179,7 +184,9 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { public init(contentsOf url: URL, options readOptionsMask: ReadingOptions = []) throws { super.init() let (data, _) = try NSData.contentsOf(url: url, options: readOptionsMask) - _init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true) + withExtendedLifetime(data) { + _init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true) + } } /// Initializes a data object with the data from the location specified by a given URL. @@ -187,7 +194,9 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { super.init() do { let (data, _) = try NSData.contentsOf(url: url) - _init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true) + withExtendedLifetime(data) { + _init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true) + } } catch { return nil } @@ -375,15 +384,19 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { guard let data = aDecoder._decodePropertyListForKey("NS.data") as? NSData else { return nil } - _init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true) + withExtendedLifetime(data) { + _init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true) + } } else { let result : Data? = aDecoder.withDecodedUnsafeBufferPointer(forKey: "NS.bytes") { guard let buffer = $0 else { return nil } return Data(buffer: buffer) } - guard let r = result else { return nil } - _init(bytes: UnsafeMutableRawPointer(mutating: r._nsObject.bytes), length: r.count, copy: true) + guard var r = result else { return nil } + r.withUnsafeMutableBytes { + _init(bytes: $0.baseAddress, length: $0.count, copy: true) + } } } @@ -559,24 +572,27 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { /// Finds and returns the range of the first occurrence of the given data, within the given range, subject to given options. open func range(of dataToFind: Data, options mask: SearchOptions = [], in searchRange: NSRange) -> NSRange { let dataToFind = dataToFind._nsObject - guard dataToFind.length > 0 else {return NSRange(location: NSNotFound, length: 0)} - guard let searchRange = Range(searchRange) else {fatalError("invalid range")} - - precondition(searchRange.upperBound <= self.length, "range outside the bounds of data") - let basePtr = self.bytes.bindMemory(to: UInt8.self, capacity: self.length) - let baseData = UnsafeBufferPointer(start: basePtr, count: self.length)[searchRange] - let searchPtr = dataToFind.bytes.bindMemory(to: UInt8.self, capacity: dataToFind.length) - let search = UnsafeBufferPointer(start: searchPtr, count: dataToFind.length) - - let location : Int? - let anchored = mask.contains(.anchored) - if mask.contains(.backwards) { - location = NSData.searchSubSequence(search.reversed(), inSequence: baseData.reversed(),anchored : anchored).map {$0.base-search.count} - } else { - location = NSData.searchSubSequence(search, inSequence: baseData,anchored : anchored) + return withExtendedLifetime(dataToFind) { + guard dataToFind.length > 0 else {return NSRange(location: NSNotFound, length: 0)} + guard let searchRange = Range(searchRange) else {fatalError("invalid range")} + + precondition(searchRange.upperBound <= self.length, "range outside the bounds of data") + + let basePtr = self.bytes.bindMemory(to: UInt8.self, capacity: self.length) + let baseData = UnsafeBufferPointer(start: basePtr, count: self.length)[searchRange] + let searchPtr = dataToFind.bytes.bindMemory(to: UInt8.self, capacity: dataToFind.length) + let search = UnsafeBufferPointer(start: searchPtr, count: dataToFind.length) + + let location : Int? + let anchored = mask.contains(.anchored) + if mask.contains(.backwards) { + location = NSData.searchSubSequence(search.reversed(), inSequence: baseData.reversed(),anchored : anchored).map {$0.base-search.count} + } else { + location = NSData.searchSubSequence(search, inSequence: baseData,anchored : anchored) + } + return location.map {NSRange(location: $0, length: search.count)} ?? NSRange(location: NSNotFound, length: 0) } - return location.map {NSRange(location: $0, length: search.count)} ?? NSRange(location: NSNotFound, length: 0) } private static func searchSubSequence(_ subSequence : T2, inSequence seq: T,anchored : Bool) -> T.Index? where T.Iterator.Element : Equatable, T.Iterator.Element == T2.Iterator.Element { diff --git a/Sources/Foundation/NSString.swift b/Sources/Foundation/NSString.swift index 6cc4539d66..9644eb7a79 100644 --- a/Sources/Foundation/NSString.swift +++ b/Sources/Foundation/NSString.swift @@ -1368,12 +1368,16 @@ extension NSString { public convenience init(contentsOf url: URL, encoding enc: UInt) throws { let readResult = try NSData(contentsOf: url, options: []) - let bytePtr = readResult.bytes.bindMemory(to: UInt8.self, capacity: readResult.length) - guard let cf = CFStringCreateWithBytes(kCFAllocatorDefault, bytePtr, readResult.length, CFStringConvertNSStringEncodingToEncoding(numericCast(enc)), true) else { - throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInapplicableStringEncoding.rawValue, userInfo: [ - NSDebugDescriptionErrorKey : "Unable to create a string using the specified encoding." - ]) + let cf = try withExtendedLifetime(readResult) { _ -> CFString in + let bytePtr = readResult.bytes.bindMemory(to: UInt8.self, capacity: readResult.length) + guard let cf = CFStringCreateWithBytes(kCFAllocatorDefault, bytePtr, readResult.length, CFStringConvertNSStringEncodingToEncoding(numericCast(enc)), true) else { + throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInapplicableStringEncoding.rawValue, userInfo: [ + NSDebugDescriptionErrorKey : "Unable to create a string using the specified encoding." + ]) + } + return cf } + var str: String? if String._conditionallyBridgeFromObjectiveC(cf._nsObject, result: &str) { self.init(str!) @@ -1391,44 +1395,49 @@ extension NSString { public convenience init(contentsOf url: URL, usedEncoding enc: UnsafeMutablePointer?) throws { let (readResult, textEncodingNameMaybe) = try NSData.contentsOf(url: url) - let encoding: UInt - let offset: Int - // Look for a BOM (Byte Order Marker) to try and determine the text Encoding, this also skips - // over the bytes. This takes precedence over the textEncoding in the http header - let bytePtr = readResult.bytes.bindMemory(to: UInt8.self, capacity:readResult.length) - if readResult.length >= 4 && bytePtr[0] == 0xFF && bytePtr[1] == 0xFE && bytePtr[2] == 0x00 && bytePtr[3] == 0x00 { - encoding = String.Encoding.utf32LittleEndian.rawValue - offset = 4 - } - else if readResult.length >= 2 && bytePtr[0] == 0xFE && bytePtr[1] == 0xFF { - encoding = String.Encoding.utf16BigEndian.rawValue - offset = 2 - } - else if readResult.length >= 2 && bytePtr[0] == 0xFF && bytePtr[1] == 0xFE { - encoding = String.Encoding.utf16LittleEndian.rawValue - offset = 2 - } - else if readResult.length >= 4 && bytePtr[0] == 0x00 && bytePtr[1] == 0x00 && bytePtr[2] == 0xFE && bytePtr[3] == 0xFF { - encoding = String.Encoding.utf32BigEndian.rawValue - offset = 4 - } - else if let charSet = textEncodingNameMaybe, let textEncoding = String.Encoding(charSet: charSet) { - encoding = textEncoding.rawValue - offset = 0 - } else { - //Need to work on more conditions. This should be the default - encoding = String.Encoding.utf8.rawValue - offset = 0 - } - // Since the encoding being passed includes the byte order the BOM wont be checked or skipped, so pass offset to - // manually skip the BOM header. - guard let cf = CFStringCreateWithBytes(kCFAllocatorDefault, bytePtr + offset, readResult.length - offset, - CFStringConvertNSStringEncodingToEncoding(numericCast(encoding)), true) else { - throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInapplicableStringEncoding.rawValue, userInfo: [ - NSDebugDescriptionErrorKey : "Unable to create a string using the specified encoding." - ]) + let (cf, encoding) = try withExtendedLifetime(readResult) { _ -> (CFString, UInt) in + let encoding: UInt + let offset: Int + // Look for a BOM (Byte Order Marker) to try and determine the text Encoding, this also skips + // over the bytes. This takes precedence over the textEncoding in the http header + let bytePtr = readResult.bytes.bindMemory(to: UInt8.self, capacity:readResult.length) + if readResult.length >= 4 && bytePtr[0] == 0xFF && bytePtr[1] == 0xFE && bytePtr[2] == 0x00 && bytePtr[3] == 0x00 { + encoding = String.Encoding.utf32LittleEndian.rawValue + offset = 4 + } + else if readResult.length >= 2 && bytePtr[0] == 0xFE && bytePtr[1] == 0xFF { + encoding = String.Encoding.utf16BigEndian.rawValue + offset = 2 + } + else if readResult.length >= 2 && bytePtr[0] == 0xFF && bytePtr[1] == 0xFE { + encoding = String.Encoding.utf16LittleEndian.rawValue + offset = 2 + } + else if readResult.length >= 4 && bytePtr[0] == 0x00 && bytePtr[1] == 0x00 && bytePtr[2] == 0xFE && bytePtr[3] == 0xFF { + encoding = String.Encoding.utf32BigEndian.rawValue + offset = 4 + } + else if let charSet = textEncodingNameMaybe, let textEncoding = String.Encoding(charSet: charSet) { + encoding = textEncoding.rawValue + offset = 0 + } else { + //Need to work on more conditions. This should be the default + encoding = String.Encoding.utf8.rawValue + offset = 0 + } + + // Since the encoding being passed includes the byte order the BOM wont be checked or skipped, so pass offset to + // manually skip the BOM header. + guard let cf = CFStringCreateWithBytes(kCFAllocatorDefault, bytePtr + offset, readResult.length - offset, + CFStringConvertNSStringEncodingToEncoding(numericCast(encoding)), true) else { + throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileReadInapplicableStringEncoding.rawValue, userInfo: [ + NSDebugDescriptionErrorKey : "Unable to create a string using the specified encoding." + ]) + } + return (cf, encoding) } + var str: String? if String._conditionallyBridgeFromObjectiveC(cf._nsObject, result: &str) { self.init(str!) diff --git a/Sources/Foundation/NSTimeZone.swift b/Sources/Foundation/NSTimeZone.swift index 8f710d6229..c2ee5350fe 100644 --- a/Sources/Foundation/NSTimeZone.swift +++ b/Sources/Foundation/NSTimeZone.swift @@ -95,7 +95,10 @@ open class NSTimeZone : NSObject, NSCopying, NSSecureCoding, NSCoding { public convenience init?(abbreviation: String) { let abbr = abbreviation._cfObject - guard let name = unsafeBitCast(CFDictionaryGetValue(CFTimeZoneCopyAbbreviationDictionary(), unsafeBitCast(abbr, to: UnsafeRawPointer.self)), to: NSString?.self) else { + let possibleName: NSString? = withExtendedLifetime(abbr) { + return unsafeBitCast(CFDictionaryGetValue(CFTimeZoneCopyAbbreviationDictionary(), unsafeBitCast(abbr, to: UnsafeRawPointer.self)), to: NSString?.self) + } + guard let name = possibleName else { return nil } self.init(name: name._swiftObject , data: nil) diff --git a/Sources/FoundationNetworking/NSURLRequest.swift b/Sources/FoundationNetworking/NSURLRequest.swift index 481ea3d45f..f97b7f7295 100644 --- a/Sources/FoundationNetworking/NSURLRequest.swift +++ b/Sources/FoundationNetworking/NSURLRequest.swift @@ -237,8 +237,10 @@ open class NSURLRequest : NSObject, NSSecureCoding, NSCopying, NSMutableCopying aCoder.encode(self.cachePolicy.rawValue._bridgeToObjectiveC(), forKey: "NS._cachePolicy") aCoder.encode(self.timeoutInterval._bridgeToObjectiveC(), forKey: "NS._timeoutInterval") if let httpBody = self.httpBody?._bridgeToObjectiveC() { - let bytePtr = httpBody.bytes.bindMemory(to: UInt8.self, capacity: httpBody.length) - aCoder.encodeBytes(bytePtr, length: httpBody.length, forKey: "NS.httpBody") + withExtendedLifetime(httpBody) { + let bytePtr = httpBody.bytes.bindMemory(to: UInt8.self, capacity: httpBody.length) + aCoder.encodeBytes(bytePtr, length: httpBody.length, forKey: "NS.httpBody") + } } //On macOS input stream is not encoded. aCoder.encode(self.networkServiceType.rawValue._bridgeToObjectiveC(), forKey: "NS._networkServiceType") diff --git a/Sources/FoundationNetworking/URLSession/NativeProtocol.swift b/Sources/FoundationNetworking/URLSession/NativeProtocol.swift index 5cf0637ee8..53a195f5a8 100644 --- a/Sources/FoundationNetworking/URLSession/NativeProtocol.swift +++ b/Sources/FoundationNetworking/URLSession/NativeProtocol.swift @@ -256,7 +256,9 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate { if case .inMemory(let bodyData) = bodyDataDrain { var data = Data() if let body = bodyData { - data = Data(bytes: body.bytes, count: body.length) + withExtendedLifetime(body) { + data = Data(bytes: body.bytes, count: body.length) + } } self.client?.urlProtocol(self, didLoad: data) self.internalState = .taskCompleted diff --git a/Sources/FoundationNetworking/URLSession/NetworkingSpecific.swift b/Sources/FoundationNetworking/URLSession/NetworkingSpecific.swift index 419c9ddb43..e534cd9218 100644 --- a/Sources/FoundationNetworking/URLSession/NetworkingSpecific.swift +++ b/Sources/FoundationNetworking/URLSession/NetworkingSpecific.swift @@ -85,7 +85,7 @@ class _NSNonfileURLContentLoader: _NSNonfileURLContentLoading { switch statusCode { // These are the only valid response codes that data will be returned for, all other codes will be treated as error. case 101, 200...399, 401, 407: - return (NSData(bytes: UnsafeMutableRawPointer(mutating: (data as NSData).bytes), length: data.count), urlResponse?.textEncodingName) + return (data as NSData, urlResponse?.textEncodingName) default: break diff --git a/Tests/Foundation/Tests/TestNSArray.swift b/Tests/Foundation/Tests/TestNSArray.swift index 13113771e8..8fdd122b2f 100644 --- a/Tests/Foundation/Tests/TestNSArray.swift +++ b/Tests/Foundation/Tests/TestNSArray.swift @@ -833,7 +833,9 @@ class TestNSArray : XCTestCase { func test_arrayUsedAsCFArrayInvokesArrayMethods() { let number = 789 as NSNumber let array = NSMutableArray(array: [123, 456]) - CFArraySetValueAtIndex(unsafeBitCast(array, to: CFMutableArray.self), 1, UnsafeRawPointer(Unmanaged.passUnretained(number).toOpaque())) + withExtendedLifetime(number) { + CFArraySetValueAtIndex(unsafeBitCast(array, to: CFMutableArray.self), 1, UnsafeRawPointer(Unmanaged.passUnretained(number).toOpaque())) + } XCTAssertEqual(array[0] as! NSNumber, 123 as NSNumber) XCTAssertEqual(array[1] as! NSNumber, 789 as NSNumber) } diff --git a/Tests/Foundation/Tests/TestNSData.swift b/Tests/Foundation/Tests/TestNSData.swift index 5e77aee13f..56517378ec 100644 --- a/Tests/Foundation/Tests/TestNSData.swift +++ b/Tests/Foundation/Tests/TestNSData.swift @@ -1049,8 +1049,10 @@ class TestNSData: LoopbackServerTest { } let replacement = makeData([8, 9, 10]) - mData.replaceBytes(in: NSRange(location: 1, length: 3), withBytes: replacement.bytes, + withExtendedLifetime(replacement) { + mData.replaceBytes(in: NSRange(location: 1, length: 3), withBytes: replacement.bytes, length: 3) + } let expected = makeData([0, 8, 9, 10, 0]) XCTAssertEqual(mData, expected) } @@ -1560,10 +1562,12 @@ extension TestNSData { let contents = NSData(contentsOfFile: filename) XCTAssertNotNil(contents) if let contents = contents { - let ptr = UnsafeMutableRawPointer(mutating: contents.bytes) - let str = String(bytesNoCopy: ptr, length: contents.length, - encoding: .ascii, freeWhenDone: false) - XCTAssertEqual(str, "swift-corelibs-foundation") + withExtendedLifetime(contents) { + let ptr = UnsafeMutableRawPointer(mutating: contents.bytes) + let str = String(bytesNoCopy: ptr, length: contents.length, + encoding: .ascii, freeWhenDone: false) + XCTAssertEqual(str, "swift-corelibs-foundation") + } } } @@ -1575,14 +1579,16 @@ extension TestNSData { let contents = NSData(contentsOfFile: "/proc/self/cmdline") XCTAssertNotNil(contents) if let contents = contents { - XCTAssertTrue(contents.length > 0) - let ptr = UnsafeMutableRawPointer(mutating: contents.bytes) - var zeroIdx = contents.range(of: Data([0]), in: NSMakeRange(0, contents.length)).location - if zeroIdx == NSNotFound { zeroIdx = contents.length } - if let str = String(bytesNoCopy: ptr, length: zeroIdx, encoding: .ascii, freeWhenDone: false) { - XCTAssertTrue(str.hasSuffix("TestFoundation")) - } else { - XCTFail("Cant create String") + withExtendedLifetime(contents) { + XCTAssertTrue(contents.length > 0) + let ptr = UnsafeMutableRawPointer(mutating: contents.bytes) + var zeroIdx = contents.range(of: Data([0]), in: NSMakeRange(0, contents.length)).location + if zeroIdx == NSNotFound { zeroIdx = contents.length } + if let str = String(bytesNoCopy: ptr, length: zeroIdx, encoding: .ascii, freeWhenDone: false) { + XCTAssertTrue(str.hasSuffix("TestFoundation")) + } else { + XCTFail("Cant create String") + } } } diff --git a/Tests/Foundation/Tests/TestXMLDocument.swift b/Tests/Foundation/Tests/TestXMLDocument.swift index e513052f4c..a417e1624e 100644 --- a/Tests/Foundation/Tests/TestXMLDocument.swift +++ b/Tests/Foundation/Tests/TestXMLDocument.swift @@ -257,45 +257,48 @@ class TestXMLDocument : LoopbackServerTest { let uriNs2 = "http://example.com/ns2" let root = XMLNode.element(withName: "root") as! XMLElement - root.addNamespace(XMLNode.namespace(withName: "ns1", stringValue: uriNs1) as! XMLNode) - - let element = XMLNode.element(withName: "element") as! XMLElement - element.addNamespace(XMLNode.namespace(withName: "ns2", stringValue: uriNs2) as! XMLNode) - root.addChild(element) - - // Add attributes without URI - element.addAttribute(XMLNode.attribute(withName: "name", stringValue: "John") as! XMLNode) - element.addAttribute(XMLNode.attribute(withName: "ns1:name", stringValue: "Tom") as! XMLNode) - - // Add attributes with URI - element.addAttribute(XMLNode.attribute(withName: "ns1:age", uri: uriNs1, stringValue: "44") as! XMLNode) - element.addAttribute(XMLNode.attribute(withName: "ns2:address", uri: uriNs2, stringValue: "Foobar City") as! XMLNode) - - // Retrieve attributes without URI - XCTAssertEqual(element.attribute(forName: "name")?.stringValue, "John", "name==John") - XCTAssertEqual(element.attribute(forName: "ns1:name")?.stringValue, "Tom", "ns1:name==Tom") - XCTAssertEqual(element.attribute(forName: "ns1:age")?.stringValue, "44", "ns1:age==44") - XCTAssertEqual(element.attribute(forName: "ns2:address")?.stringValue, "Foobar City", "ns2:addresss==Foobar City") - - // Retrieve attributes with URI - XCTAssertEqual(element.attribute(forLocalName: "name", uri: nil)?.stringValue, "John", "name==John") - XCTAssertEqual(element.attribute(forLocalName: "name", uri: uriNs1)?.stringValue, "Tom", "name==Tom") - XCTAssertEqual(element.attribute(forLocalName: "age", uri: uriNs1)?.stringValue, "44", "age==44") - XCTAssertNil(element.attribute(forLocalName: "address", uri: uriNs1), "address==nil") - XCTAssertEqual(element.attribute(forLocalName: "address", uri: uriNs2)?.stringValue, "Foobar City", "addresss==Foobar City") - - // Overwrite attributes - element.addAttribute(XMLNode.attribute(withName: "ns1:age", stringValue: "33") as! XMLNode) - XCTAssertEqual(element.attribute(forName: "ns1:age")?.stringValue, "33", "ns1:age==33") - element.addAttribute(XMLNode.attribute(withName: "ns1:name", uri: uriNs1, stringValue: "Tommy") as! XMLNode) - XCTAssertEqual(element.attribute(forLocalName: "name", uri: uriNs1)?.stringValue, "Tommy", "ns1:name==Tommy") - - // Remove attributes - element.removeAttribute(forName: "name") - XCTAssertNil(element.attribute(forLocalName: "name", uri: nil), "name removed") - XCTAssertNotNil(element.attribute(forLocalName: "name", uri: uriNs1), "ns1:name not removed") - element.removeAttribute(forName: "ns1:name") - XCTAssertNil(element.attribute(forLocalName: "name", uri: uriNs1), "ns1:name removed") + + withExtendedLifetime(root) { + root.addNamespace(XMLNode.namespace(withName: "ns1", stringValue: uriNs1) as! XMLNode) + + let element = XMLNode.element(withName: "element") as! XMLElement + element.addNamespace(XMLNode.namespace(withName: "ns2", stringValue: uriNs2) as! XMLNode) + root.addChild(element) + + // Add attributes without URI + element.addAttribute(XMLNode.attribute(withName: "name", stringValue: "John") as! XMLNode) + element.addAttribute(XMLNode.attribute(withName: "ns1:name", stringValue: "Tom") as! XMLNode) + + // Add attributes with URI + element.addAttribute(XMLNode.attribute(withName: "ns1:age", uri: uriNs1, stringValue: "44") as! XMLNode) + element.addAttribute(XMLNode.attribute(withName: "ns2:address", uri: uriNs2, stringValue: "Foobar City") as! XMLNode) + + // Retrieve attributes without URI + XCTAssertEqual(element.attribute(forName: "name")?.stringValue, "John", "name==John") + XCTAssertEqual(element.attribute(forName: "ns1:name")?.stringValue, "Tom", "ns1:name==Tom") + XCTAssertEqual(element.attribute(forName: "ns1:age")?.stringValue, "44", "ns1:age==44") + XCTAssertEqual(element.attribute(forName: "ns2:address")?.stringValue, "Foobar City", "ns2:addresss==Foobar City") + + // Retrieve attributes with URI + XCTAssertEqual(element.attribute(forLocalName: "name", uri: nil)?.stringValue, "John", "name==John") + XCTAssertEqual(element.attribute(forLocalName: "name", uri: uriNs1)?.stringValue, "Tom", "name==Tom") + XCTAssertEqual(element.attribute(forLocalName: "age", uri: uriNs1)?.stringValue, "44", "age==44") + XCTAssertNil(element.attribute(forLocalName: "address", uri: uriNs1), "address==nil") + XCTAssertEqual(element.attribute(forLocalName: "address", uri: uriNs2)?.stringValue, "Foobar City", "addresss==Foobar City") + + // Overwrite attributes + element.addAttribute(XMLNode.attribute(withName: "ns1:age", stringValue: "33") as! XMLNode) + XCTAssertEqual(element.attribute(forName: "ns1:age")?.stringValue, "33", "ns1:age==33") + element.addAttribute(XMLNode.attribute(withName: "ns1:name", uri: uriNs1, stringValue: "Tommy") as! XMLNode) + XCTAssertEqual(element.attribute(forLocalName: "name", uri: uriNs1)?.stringValue, "Tommy", "ns1:name==Tommy") + + // Remove attributes + element.removeAttribute(forName: "name") + XCTAssertNil(element.attribute(forLocalName: "name", uri: nil), "name removed") + XCTAssertNotNil(element.attribute(forLocalName: "name", uri: uriNs1), "ns1:name not removed") + element.removeAttribute(forName: "ns1:name") + XCTAssertNil(element.attribute(forLocalName: "name", uri: uriNs1), "ns1:name removed") + } } func test_comments() {