Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions Sources/FoundationEssentials/Data/Data.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2255,11 +2255,19 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)),
count: _representation.count
)
case .large(let slice):
case .large(var slice):
// Clear _representation during the unique check to avoid double counting the reference, and assign the mutated slice back to _representation afterwards
_representation = .empty
slice.ensureUniqueReference()
_representation = .large(slice)
buffer = unsafe UnsafeMutableRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
case .slice(let slice):
case .slice(var slice):
// Clear _representation during the unique check to avoid double counting the reference, and assign the mutated slice back to _representation afterwards
_representation = .empty
slice.ensureUniqueReference()
_representation = .slice(slice)
buffer = unsafe UnsafeMutableRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
Expand Down Expand Up @@ -2288,11 +2296,19 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)),
count: _representation.count
)
case .large(let slice):
case .large(var slice):
// Clear _representation during the unique check to avoid double counting the reference, and assign the mutated slice back to _representation afterwards
_representation = .empty
slice.ensureUniqueReference()
_representation = .large(slice)
buffer = unsafe UnsafeMutableRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
case .slice(let slice):
case .slice(var slice):
// Clear _representation during the unique check to avoid double counting the reference, and assign the mutated slice back to _representation afterwards
_representation = .empty
slice.ensureUniqueReference()
_representation = .slice(slice)
buffer = unsafe UnsafeMutableRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
Expand Down
44 changes: 44 additions & 0 deletions Tests/FoundationEssentialsTests/DataTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,50 @@ private final class DataTests {
#expect(data.count == 0)
}
}

@Test func validateMutation_cow_mutableBytes() {
var data = Data(Array(repeating: 0, count: 32))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have Data(count: 32) to get a zero-initialized instance?

holdReference(data) {
var bytes = data.mutableBytes
bytes.storeBytes(of: 1, toByteOffset: 0, as: UInt8.self)

#expect(data[0] == 1)
#expect(heldData?[0] == 0)
}

var data2 = Data(Array(repeating: 0, count: 32))
// Escape the pointer to compare after a mutation without dereferencing the pointer
let originalPointer = data2.withUnsafeBytes { $0.baseAddress }

var bytes = data2.mutableBytes
bytes.storeBytes(of: 1, toByteOffset: 0, as: UInt8.self)
#expect(data2[0] == 1)
data2.withUnsafeBytes {
#expect($0.baseAddress == originalPointer)
}
}

@Test func validateMutation_cow_mutableSpan() {
var data = Data(Array(repeating: 0, count: 32))
holdReference(data) {
var bytes = data.mutableSpan
bytes[0] = 1

#expect(data[0] == 1)
#expect(heldData?[0] == 0)
}

var data2 = Data(Array(repeating: 0, count: 32))
// Escape the pointer to compare after a mutation without dereferencing the pointer
let originalPointer = data2.withUnsafeBytes { $0.baseAddress }

var bytes = data2.mutableSpan
bytes[0] = 1
#expect(data2[0] == 1)
data2.withUnsafeBytes {
#expect($0.baseAddress == originalPointer)
}
}

@Test func sliceHash() {
let base1 = Data([0, 0xFF, 0xFF, 0])
Expand Down