Skip to content

Commit abfcc28

Browse files
authored
Merge pull request #1531 from swiftlang/automerge/merge-main-2025-10-02_09-03
Merge `release/6.2` into `main`
2 parents b2ab762 + a8cdd87 commit abfcc28

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])
@@ -2505,17 +2549,16 @@ extension DataTests {
25052549
// 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
25062550
@Suite("Large Data Tests", .serialized)
25072551
struct LargeDataTests {
2508-
@Test
2509-
func largeSliceDataSpan() throws {
25102552
#if _pointerBitWidth(_64)
2511-
let count = Int(Int32.max)
2553+
let largeCount = Int(Int32.max)
25122554
#elseif _pointerBitWidth(_32)
2513-
let count = Int(Int16.max)
2555+
let largeCount = Int(Int16.max)
25142556
#else
25152557
#error("This test needs updating")
25162558
#endif
2517-
2518-
let source = Data(repeating: 0, count: count).dropFirst()
2559+
@Test
2560+
func largeSliceDataSpan() throws {
2561+
let source = Data(repeating: 0, count: largeCount).dropFirst()
25192562
#expect(source.startIndex != 0)
25202563
let span = source.span
25212564
let isEmpty = span.isEmpty
@@ -2524,20 +2567,11 @@ struct LargeDataTests {
25242567

25252568
@Test
25262569
func largeSliceDataMutableSpan() throws {
2527-
#if _pointerBitWidth(_64)
2528-
var count = Int(Int32.max)
2529-
#elseif _pointerBitWidth(_32)
2530-
var count = Int(Int16.max)
2531-
#else
2532-
#error("This test needs updating")
2533-
#endif
2534-
25352570
#if !canImport(Darwin) || FOUNDATION_FRAMEWORK
2536-
var source = Data(repeating: 0, count: count).dropFirst()
2571+
var source = Data(repeating: 0, count: largeCount).dropFirst()
25372572
#expect(source.startIndex != 0)
2538-
count = source.count
25392573
var span = source.mutableSpan
2540-
#expect(span.count == count)
2574+
#expect(span.count == largeCount - 1)
25412575
let i = try #require(span.indices.dropFirst().randomElement())
25422576
span[i] = .max
25432577
#expect(source[i] == 0)
@@ -2547,23 +2581,62 @@ struct LargeDataTests {
25472581

25482582
@Test
25492583
func largeSliceDataMutableRawSpan() throws {
2550-
#if _pointerBitWidth(_64)
2551-
var count = Int(Int32.max)
2552-
#elseif _pointerBitWidth(_32)
2553-
var count = Int(Int16.max)
2554-
#else
2555-
#error("This test needs updating")
2556-
#endif
2557-
2558-
var source = Data(repeating: 0, count: count).dropFirst()
2584+
var source = Data(repeating: 0, count: largeCount).dropFirst()
25592585
#expect(source.startIndex != 0)
2560-
count = source.count
25612586
var span = source.mutableBytes
25622587
let byteCount = span.byteCount
2563-
#expect(byteCount == count)
2588+
#expect(byteCount == largeCount - 1)
25642589
let i = try #require(span.byteOffsets.dropFirst().randomElement())
25652590
span.storeBytes(of: -1, toByteOffset: i, as: Int8.self)
25662591
#expect(source[i] == 0)
25672592
#expect(source[i+1] == .max)
25682593
}
2594+
2595+
@Test func validateMutation_cow_largeMutableBytes() {
2596+
// Avoid copying a large data on platforms with constrained memory limits
2597+
#if !canImport(Darwin) || os(macOS)
2598+
var data = Data(count: largeCount)
2599+
let heldData = data
2600+
var bytes = data.mutableBytes
2601+
bytes.storeBytes(of: 1, toByteOffset: 0, as: UInt8.self)
2602+
2603+
#expect(data[0] == 1)
2604+
#expect(heldData[0] == 0)
2605+
#endif
2606+
2607+
var data2 = Data(count: largeCount)
2608+
// Escape the pointer to compare after a mutation without dereferencing the pointer
2609+
let originalPointer = data2.withUnsafeBytes { $0.baseAddress }
2610+
2611+
var bytes2 = data2.mutableBytes
2612+
bytes2.storeBytes(of: 1, toByteOffset: 0, as: UInt8.self)
2613+
#expect(data2[0] == 1)
2614+
data2.withUnsafeBytes {
2615+
#expect($0.baseAddress == originalPointer)
2616+
}
2617+
}
2618+
2619+
@Test func validateMutation_cow_largeMutableSpan() {
2620+
// Avoid copying a large data on platforms with constrained memory limits
2621+
#if !canImport(Darwin) || os(macOS)
2622+
var data = Data(count: largeCount)
2623+
let heldData = data
2624+
var bytes = data.mutableSpan
2625+
bytes[0] = 1
2626+
2627+
#expect(data[0] == 1)
2628+
#expect(heldData[0] == 0)
2629+
#endif
2630+
2631+
var data2 = Data(count: largeCount)
2632+
// Escape the pointer to compare after a mutation without dereferencing the pointer
2633+
let originalPointer = data2.withUnsafeBytes { $0.baseAddress }
2634+
2635+
var bytes2 = data2.mutableSpan
2636+
bytes2[0] = 1
2637+
#expect(data2[0] == 1)
2638+
data2.withUnsafeBytes {
2639+
#expect($0.baseAddress == originalPointer)
2640+
}
2641+
}
25692642
}

0 commit comments

Comments
 (0)