Skip to content

Commit a8cdd87

Browse files
authored
Data.mutableSpan and Data.mutableBytes do not perform uniqueness check (#1530)
* (161666595) Data.mutableSpan and Data.mutableBytes do not perform uniqueness check * Address feedback * Fix Linux build failure * Fix test failure
1 parent a8055a5 commit a8cdd87

File tree

2 files changed

+121
-32
lines changed

2 files changed

+121
-32
lines changed

Sources/FoundationEssentials/Data/Data.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2255,11 +2255,19 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
22552255
start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)),
22562256
count: _representation.count
22572257
)
2258-
case .large(let slice):
2258+
case .large(var slice):
2259+
// Clear _representation during the unique check to avoid double counting the reference, and assign the mutated slice back to _representation afterwards
2260+
_representation = .empty
2261+
slice.ensureUniqueReference()
2262+
_representation = .large(slice)
22592263
buffer = unsafe UnsafeMutableRawBufferPointer(
22602264
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
22612265
)
2262-
case .slice(let slice):
2266+
case .slice(var slice):
2267+
// Clear _representation during the unique check to avoid double counting the reference, and assign the mutated slice back to _representation afterwards
2268+
_representation = .empty
2269+
slice.ensureUniqueReference()
2270+
_representation = .slice(slice)
22632271
buffer = unsafe UnsafeMutableRawBufferPointer(
22642272
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
22652273
)
@@ -2288,11 +2296,19 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
22882296
start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)),
22892297
count: _representation.count
22902298
)
2291-
case .large(let slice):
2299+
case .large(var slice):
2300+
// Clear _representation during the unique check to avoid double counting the reference, and assign the mutated slice back to _representation afterwards
2301+
_representation = .empty
2302+
slice.ensureUniqueReference()
2303+
_representation = .large(slice)
22922304
buffer = unsafe UnsafeMutableRawBufferPointer(
22932305
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
22942306
)
2295-
case .slice(let slice):
2307+
case .slice(var slice):
2308+
// Clear _representation during the unique check to avoid double counting the reference, and assign the mutated slice back to _representation afterwards
2309+
_representation = .empty
2310+
slice.ensureUniqueReference()
2311+
_representation = .slice(slice)
22962312
buffer = unsafe UnsafeMutableRawBufferPointer(
22972313
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
22982314
)

Tests/FoundationEssentialsTests/DataTests.swift

Lines changed: 101 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,50 @@ private final class DataTests {
14391439
#expect(data.count == 0)
14401440
}
14411441
}
1442+
1443+
@Test func validateMutation_cow_mutableBytes() {
1444+
var data = Data(count: 32)
1445+
holdReference(data) {
1446+
var bytes = data.mutableBytes
1447+
bytes.storeBytes(of: 1, toByteOffset: 0, as: UInt8.self)
1448+
1449+
#expect(data[0] == 1)
1450+
#expect(heldData?[0] == 0)
1451+
}
1452+
1453+
var data2 = Data(count: 32)
1454+
// Escape the pointer to compare after a mutation without dereferencing the pointer
1455+
let originalPointer = data2.withUnsafeBytes { $0.baseAddress }
1456+
1457+
var bytes = data2.mutableBytes
1458+
bytes.storeBytes(of: 1, toByteOffset: 0, as: UInt8.self)
1459+
#expect(data2[0] == 1)
1460+
data2.withUnsafeBytes {
1461+
#expect($0.baseAddress == originalPointer)
1462+
}
1463+
}
1464+
1465+
@Test func validateMutation_cow_mutableSpan() {
1466+
var data = Data(count: 32)
1467+
holdReference(data) {
1468+
var bytes = data.mutableSpan
1469+
bytes[0] = 1
1470+
1471+
#expect(data[0] == 1)
1472+
#expect(heldData?[0] == 0)
1473+
}
1474+
1475+
var data2 = Data(count: 32)
1476+
// Escape the pointer to compare after a mutation without dereferencing the pointer
1477+
let originalPointer = data2.withUnsafeBytes { $0.baseAddress }
1478+
1479+
var bytes = data2.mutableSpan
1480+
bytes[0] = 1
1481+
#expect(data2[0] == 1)
1482+
data2.withUnsafeBytes {
1483+
#expect($0.baseAddress == originalPointer)
1484+
}
1485+
}
14421486

14431487
@Test func sliceHash() {
14441488
let base1 = Data([0, 0xFF, 0xFF, 0])
@@ -2385,17 +2429,16 @@ extension DataTests {
23852429
// These tests require allocating an extremely large amount of data and are serialized to prevent the test runner from using all available memory at once
23862430
@Suite("Large Data Tests", .serialized)
23872431
struct LargeDataTests {
2388-
@Test
2389-
func largeSliceDataSpan() throws {
23902432
#if _pointerBitWidth(_64)
2391-
let count = Int(Int32.max)
2433+
let largeCount = Int(Int32.max)
23922434
#elseif _pointerBitWidth(_32)
2393-
let count = Int(Int16.max)
2435+
let largeCount = Int(Int16.max)
23942436
#else
23952437
#error("This test needs updating")
23962438
#endif
2397-
2398-
let source = Data(repeating: 0, count: count).dropFirst()
2439+
@Test
2440+
func largeSliceDataSpan() throws {
2441+
let source = Data(repeating: 0, count: largeCount).dropFirst()
23992442
#expect(source.startIndex != 0)
24002443
let span = source.span
24012444
let isEmpty = span.isEmpty
@@ -2404,20 +2447,11 @@ struct LargeDataTests {
24042447

24052448
@Test
24062449
func largeSliceDataMutableSpan() throws {
2407-
#if _pointerBitWidth(_64)
2408-
var count = Int(Int32.max)
2409-
#elseif _pointerBitWidth(_32)
2410-
var count = Int(Int16.max)
2411-
#else
2412-
#error("This test needs updating")
2413-
#endif
2414-
24152450
#if !canImport(Darwin) || FOUNDATION_FRAMEWORK
2416-
var source = Data(repeating: 0, count: count).dropFirst()
2451+
var source = Data(repeating: 0, count: largeCount).dropFirst()
24172452
#expect(source.startIndex != 0)
2418-
count = source.count
24192453
var span = source.mutableSpan
2420-
#expect(span.count == count)
2454+
#expect(span.count == largeCount - 1)
24212455
let i = try #require(span.indices.dropFirst().randomElement())
24222456
span[i] = .max
24232457
#expect(source[i] == 0)
@@ -2427,23 +2461,62 @@ struct LargeDataTests {
24272461

24282462
@Test
24292463
func largeSliceDataMutableRawSpan() throws {
2430-
#if _pointerBitWidth(_64)
2431-
var count = Int(Int32.max)
2432-
#elseif _pointerBitWidth(_32)
2433-
var count = Int(Int16.max)
2434-
#else
2435-
#error("This test needs updating")
2436-
#endif
2437-
2438-
var source = Data(repeating: 0, count: count).dropFirst()
2464+
var source = Data(repeating: 0, count: largeCount).dropFirst()
24392465
#expect(source.startIndex != 0)
2440-
count = source.count
24412466
var span = source.mutableBytes
24422467
let byteCount = span.byteCount
2443-
#expect(byteCount == count)
2468+
#expect(byteCount == largeCount - 1)
24442469
let i = try #require(span.byteOffsets.dropFirst().randomElement())
24452470
span.storeBytes(of: -1, toByteOffset: i, as: Int8.self)
24462471
#expect(source[i] == 0)
24472472
#expect(source[i+1] == .max)
24482473
}
2474+
2475+
@Test func validateMutation_cow_largeMutableBytes() {
2476+
// Avoid copying a large data on platforms with constrained memory limits
2477+
#if !canImport(Darwin) || os(macOS)
2478+
var data = Data(count: largeCount)
2479+
let heldData = data
2480+
var bytes = data.mutableBytes
2481+
bytes.storeBytes(of: 1, toByteOffset: 0, as: UInt8.self)
2482+
2483+
#expect(data[0] == 1)
2484+
#expect(heldData[0] == 0)
2485+
#endif
2486+
2487+
var data2 = Data(count: largeCount)
2488+
// Escape the pointer to compare after a mutation without dereferencing the pointer
2489+
let originalPointer = data2.withUnsafeBytes { $0.baseAddress }
2490+
2491+
var bytes2 = data2.mutableBytes
2492+
bytes2.storeBytes(of: 1, toByteOffset: 0, as: UInt8.self)
2493+
#expect(data2[0] == 1)
2494+
data2.withUnsafeBytes {
2495+
#expect($0.baseAddress == originalPointer)
2496+
}
2497+
}
2498+
2499+
@Test func validateMutation_cow_largeMutableSpan() {
2500+
// Avoid copying a large data on platforms with constrained memory limits
2501+
#if !canImport(Darwin) || os(macOS)
2502+
var data = Data(count: largeCount)
2503+
let heldData = data
2504+
var bytes = data.mutableSpan
2505+
bytes[0] = 1
2506+
2507+
#expect(data[0] == 1)
2508+
#expect(heldData[0] == 0)
2509+
#endif
2510+
2511+
var data2 = Data(count: largeCount)
2512+
// Escape the pointer to compare after a mutation without dereferencing the pointer
2513+
let originalPointer = data2.withUnsafeBytes { $0.baseAddress }
2514+
2515+
var bytes2 = data2.mutableSpan
2516+
bytes2[0] = 1
2517+
#expect(data2[0] == 1)
2518+
data2.withUnsafeBytes {
2519+
#expect($0.baseAddress == originalPointer)
2520+
}
2521+
}
24492522
}

0 commit comments

Comments
 (0)