Validate indexes and ranges passed into Data so that bounding conditions are respected (#1061)
diff --git a/Foundation/Data.swift b/Foundation/Data.swift
index b4d22c1..fd5b4a4 100644
--- a/Foundation/Data.swift
+++ b/Foundation/Data.swift
@@ -319,6 +319,7 @@
@inline(__always)
public func append(_ bytes: UnsafeRawPointer, length: Int) {
+ precondition(length >= 0, "Length of appending bytes must be positive")
switch _backing {
case .swift:
let origLength = _length
@@ -1087,6 +1088,18 @@
_sliceRange = range
}
+ @_versioned
+ internal func _validateIndex(_ index: Int, message: String? = nil) {
+ precondition(_sliceRange.contains(index), message ?? "Index \(index) is out of bounds of range \(_sliceRange)")
+ }
+
+ @_versioned
+ internal func _validateRange<R: RangeExpression>(_ range: R) where R.Bound == Int {
+ let r = range.relative(to: 0..<R.Bound.max)
+ precondition(r.lowerBound >= _sliceRange.lowerBound && r.lowerBound <= _sliceRange.upperBound, "Range \(r) is out of bounds of range \(_sliceRange)")
+ precondition(r.upperBound >= _sliceRange.lowerBound && r.upperBound <= _sliceRange.upperBound, "Range \(r) is out of bounds of range \(_sliceRange)")
+ }
+
// -----------------------------------
// MARK: - Properties and Functions
@@ -1099,6 +1112,7 @@
}
@inline(__always)
set {
+ precondition(count >= 0, "count must be positive")
if !isKnownUniquelyReferenced(&_backing) {
_backing = _backing.mutableCopy(_sliceRange)
}
@@ -1142,14 +1156,15 @@
/// - warning: This method does not verify that the contents at pointer have enough space to hold `count` bytes.
@inline(__always)
public func copyBytes(to pointer: UnsafeMutablePointer<UInt8>, count: Int) {
+ precondition(count >= 0, "count of bytes to copy must be positive")
if count == 0 { return }
- memcpy(UnsafeMutableRawPointer(pointer), _backing.bytes!.advanced(by: _sliceRange.lowerBound), count)
+ memcpy(UnsafeMutableRawPointer(pointer), _backing.bytes!.advanced(by: _sliceRange.lowerBound), Swift.min(count, _sliceRange.count))
}
@inline(__always)
private func _copyBytesHelper(to pointer: UnsafeMutableRawPointer, from range: NSRange) {
if range.length == 0 { return }
- memcpy(UnsafeMutableRawPointer(pointer), _backing.bytes!.advanced(by: range.location), range.length)
+ memcpy(UnsafeMutableRawPointer(pointer), _backing.bytes!.advanced(by: range.location), Swift.min(range.length, _sliceRange.count))
}
/// Copy a subset of the contents of the data to a pointer.
@@ -1175,16 +1190,11 @@
let copyRange : Range<Index>
if let r = range {
guard !r.isEmpty else { return 0 }
- precondition(r.lowerBound >= 0)
- precondition(r.lowerBound < cnt, "The range is outside the bounds of the data")
-
- precondition(r.upperBound >= 0)
- precondition(r.upperBound <= cnt, "The range is outside the bounds of the data")
-
copyRange = r.lowerBound..<(r.lowerBound + Swift.min(buffer.count * MemoryLayout<DestinationType>.stride, r.count))
} else {
copyRange = 0..<Swift.min(buffer.count * MemoryLayout<DestinationType>.stride, cnt)
}
+ _validateRange(copyRange)
guard !copyRange.isEmpty else { return 0 }
@@ -1243,6 +1253,7 @@
public func range(of dataToFind: Data, options: Data.SearchOptions = [], in range: Range<Index>? = nil) -> Range<Index>? {
let nsRange : NSRange
if let r = range {
+ _validateRange(r)
nsRange = NSMakeRange(r.lowerBound, r.upperBound - r.lowerBound)
} else {
nsRange = NSMakeRange(0, _backing.length)
@@ -1266,6 +1277,7 @@
@inline(__always)
public mutating func append(_ bytes: UnsafePointer<UInt8>, count: Int) {
+ precondition(count >= 0, "count must be positive")
if count == 0 { return }
if !isKnownUniquelyReferenced(&_backing) {
_backing = _backing.mutableCopy(_sliceRange)
@@ -1326,6 +1338,9 @@
/// - parameter range: The range in the data to set to `0`.
@inline(__always)
public mutating func resetBytes(in range: Range<Index>) {
+ // it is worth noting that the range here may be out of bounds of the Data itself (which triggers a growth)
+ precondition(range.lowerBound >= 0, "Ranges must be positive bounds")
+ precondition(range.upperBound >= 0, "Ranges must be positive bounds")
let range = NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)
if !isKnownUniquelyReferenced(&_backing) {
_backing = _backing.mutableCopy(_sliceRange)
@@ -1346,6 +1361,7 @@
/// - parameter data: The replacement data.
@inline(__always)
public mutating func replaceSubrange(_ subrange: Range<Index>, with data: Data) {
+ _validateRange(subrange)
let nsRange = NSMakeRange(subrange.lowerBound, subrange.upperBound - subrange.lowerBound)
let cnt = data.count
if !isKnownUniquelyReferenced(&_backing) {
@@ -1361,6 +1377,7 @@
@inline(__always)
public mutating func replaceSubrange(_ subrange: CountableRange<Index>, with data: Data) {
+ _validateRange(subrange)
let nsRange = NSMakeRange(subrange.lowerBound, subrange.upperBound - subrange.lowerBound)
let cnt = data.count
if !isKnownUniquelyReferenced(&_backing) {
@@ -1383,6 +1400,7 @@
/// - parameter buffer: The replacement bytes.
@inline(__always)
public mutating func replaceSubrange<SourceType>(_ subrange: Range<Index>, with buffer: UnsafeBufferPointer<SourceType>) {
+ _validateRange(subrange)
let nsRange = NSMakeRange(subrange.lowerBound, subrange.upperBound - subrange.lowerBound)
let bufferCount = buffer.count * MemoryLayout<SourceType>.stride
@@ -1405,19 +1423,13 @@
@inline(__always)
public mutating func replaceSubrange<ByteCollection : Collection>(_ subrange: Range<Index>, with newElements: ByteCollection)
where ByteCollection.Iterator.Element == Data.Iterator.Element {
-
+ _validateRange(subrange)
// Calculate this once, it may not be O(1)
let replacementCount: Int = numericCast(newElements.count)
let currentCount = self.count
let subrangeCount = subrange.count
- if currentCount < subrange.lowerBound + subrangeCount {
- if subrangeCount == 0 {
- preconditionFailure("location \(subrange.lowerBound) exceeds data count \(currentCount)")
- } else {
- preconditionFailure("range \(subrange) exceeds data count \(currentCount)")
- }
- }
+ _validateRange(subrange)
let resultCount = currentCount - subrangeCount + replacementCount
if resultCount != currentCount {
@@ -1446,6 +1458,7 @@
@inline(__always)
public mutating func replaceSubrange(_ subrange: Range<Index>, with bytes: UnsafeRawPointer, count cnt: Int) {
+ _validateRange(subrange)
let nsRange = NSMakeRange(subrange.lowerBound, subrange.upperBound - subrange.lowerBound)
if !isKnownUniquelyReferenced(&_backing) {
_backing = _backing.mutableCopy(_sliceRange)
@@ -1461,11 +1474,11 @@
/// - parameter range: The range to copy.
@inline(__always)
public func subdata(in range: Range<Index>) -> Data {
+ _validateRange(range)
let length = count
if count == 0 {
return Data()
}
- precondition(length >= range.upperBound)
return _backing.subdata(in: range)
}
@@ -1502,6 +1515,7 @@
@inline(__always)
public func advanced(by amount: Int) -> Data {
+ _validateIndex(startIndex + amount)
let length = count - amount
precondition(length > 0)
return withUnsafeBytes { (ptr: UnsafePointer<UInt8>) -> Data in
@@ -1518,10 +1532,12 @@
public subscript(index: Index) -> UInt8 {
@inline(__always)
get {
+ _validateIndex(index)
return _backing.bytes!.advanced(by: index).assumingMemoryBound(to: UInt8.self).pointee
}
@inline(__always)
set {
+ _validateIndex(index)
if !isKnownUniquelyReferenced(&_backing) {
_backing = _backing.mutableCopy(_sliceRange)
}
@@ -1532,6 +1548,7 @@
public subscript(bounds: Range<Index>) -> Data {
@inline(__always)
get {
+ _validateRange(bounds)
return Data(backing: _backing, range: bounds)
}
@inline(__always)
@@ -1547,14 +1564,18 @@
let range = rangeExpression.relative(to: 0..<R.Bound.max)
let start: Int = numericCast(range.lowerBound)
let end: Int = numericCast(range.upperBound)
- return Data(backing: _backing, range: start..<end)
+ let r: Range<Int> = start..<end
+ _validateRange(r)
+ return Data(backing: _backing, range: r)
}
@inline(__always)
set {
let range = rangeExpression.relative(to: 0..<R.Bound.max)
let start: Int = numericCast(range.lowerBound)
let end: Int = numericCast(range.upperBound)
- replaceSubrange(start..<end, with: newValue)
+ let r: Range<Int> = start..<end
+ _validateRange(r)
+ replaceSubrange(r, with: newValue)
}
}
diff --git a/TestFoundation/TestNSData.swift b/TestFoundation/TestNSData.swift
index 7df6584..020b2c1 100644
--- a/TestFoundation/TestNSData.swift
+++ b/TestFoundation/TestNSData.swift
@@ -48,7 +48,7 @@
("testCopyBytes_oversized", testCopyBytes_oversized),
("testCopyBytes_ranges", testCopyBytes_ranges),
("testCopyBytes_undersized", testCopyBytes_undersized),
- // ("testCopyBytes", testCopyBytes), Disabled to due failure, we want this passing - but API matching is more important right now
+ ("testCopyBytes", testCopyBytes),
("testCustomDeallocator", testCustomDeallocator),
("testDataInSet", testDataInSet),
("testEquality", testEquality),