Skip to content

Commit 076effd

Browse files
author
Itai Ferber
committed
Correct buffering in .init<S> and .append<S> and add unit test
1 parent 1b5c018 commit 076effd

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

stdlib/public/Darwin/Foundation/Data.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,10 +2084,16 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
20842084
if buffer.count < buffer.capacity {
20852085
buffer.append(byte: element)
20862086
} else {
2087-
buffer.withUnsafeBytes {_representation.append(contentsOf: $0)}
2087+
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
20882088
buffer.count = 0
20892089
}
20902090
}
2091+
2092+
// If we've still got bytes left in the buffer (i.e. the loop ended before we filled up the buffer and cleared it out), append them.
2093+
if buffer.count > 0 {
2094+
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
2095+
buffer.count = 0
2096+
}
20912097
}
20922098
}
20932099
}
@@ -2372,10 +2378,16 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
23722378
if buffer.count < buffer.capacity {
23732379
buffer.append(byte: element)
23742380
} else {
2375-
buffer.withUnsafeBytes {_representation.append(contentsOf: $0)}
2381+
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
23762382
buffer.count = 0
23772383
}
23782384
}
2385+
2386+
// If we've still got bytes left in the buffer (i.e. the loop ended before we filled up the buffer and cleared it out), append them.
2387+
if buffer.count > 0 {
2388+
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
2389+
buffer.count = 0
2390+
}
23792391
}
23802392
}
23812393

test/stdlib/TestData.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,57 @@ class TestData : TestDataSuper {
11831183
expectEqual(Data(bytes: [1]), slice)
11841184
}
11851185

1186+
// This test uses `repeatElement` to produce a sequence -- the produced sequence reports its actual count as its `.underestimatedCount`.
1187+
func test_appendingNonContiguousSequence_exactCount() {
1188+
var d = Data()
1189+
1190+
// d should go from .empty representation to .inline.
1191+
// Appending a small enough sequence to fit in linline should actually be copied.
1192+
d.append(contentsOf: repeatElement(UInt8(0x01), count: 2))
1193+
expectEqual(Data([0x01, 0x01]), d)
1194+
1195+
// Appending another small sequence should similarly still work.
1196+
d.append(contentsOf: repeatElement(UInt8(0x02), count: 1))
1197+
expectEqual(Data([0x01, 0x01, 0x02]), d)
1198+
1199+
// If we append a sequence of elements larger than a single InlineData, the internal append here should buffer.
1200+
// We want to make sure that buffering in this way does not accidentally drop trailing elements on the floor.
1201+
d.append(contentsOf: repeatElement(UInt8(0x03), count: 24))
1202+
expectEqual(Data([0x01, 0x01, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
1203+
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
1204+
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03]), d)
1205+
}
1206+
1207+
// This test is like test_appendingNonContiguousSequence_exactCount but uses a sequence which reports 0 for its `.underestimatedCount`.
1208+
// This attempts to hit the worst-case scenario of `Data.append<S>(_:)` -- a discontiguous sequence of unknown length.
1209+
func test_appendingNonContiguousSequence_underestimatedCount() {
1210+
// underestimatedRepeatedElement does the same thing as repeatElement, but reports an `.underestimatedCount` of 0.
1211+
let underestimatedRepeatedElement = { (element: UInt8, count: Int) in
1212+
return sequence(state: count) { (count: inout Int) -> UInt8? in
1213+
defer { count -= 1 }
1214+
return count > 0 ? element : nil
1215+
}
1216+
}
1217+
1218+
var d = Data()
1219+
1220+
// d should go from .empty representation to .inline.
1221+
// Appending a small enough sequence to fit in linline should actually be copied.
1222+
d.append(contentsOf: underestimatedRepeatedElement(UInt8(0x01), 2))
1223+
expectEqual(Data([0x01, 0x01]), d)
1224+
1225+
// Appending another small sequence should similarly still work.
1226+
d.append(contentsOf: underestimatedRepeatedElement(UInt8(0x02), 1))
1227+
expectEqual(Data([0x01, 0x01, 0x02]), d)
1228+
1229+
// If we append a sequence of elements larger than a single InlineData, the internal append here should buffer.
1230+
// We want to make sure that buffering in this way does not accidentally drop trailing elements on the floor.
1231+
d.append(contentsOf: underestimatedRepeatedElement(UInt8(0x03), 24))
1232+
expectEqual(Data([0x01, 0x01, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
1233+
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
1234+
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03]), d)
1235+
}
1236+
11861237
func test_sequenceInitializers() {
11871238
let seq = repeatElement(UInt8(0x02), count: 3) // ensure we fall into the sequence case
11881239

@@ -3753,6 +3804,8 @@ DataTests.test("test_copyBytes1") { TestData().test_copyBytes1() }
37533804
DataTests.test("test_copyBytes2") { TestData().test_copyBytes2() }
37543805
DataTests.test("test_sliceOfSliceViaRangeExpression") { TestData().test_sliceOfSliceViaRangeExpression() }
37553806
DataTests.test("test_appendingSlices") { TestData().test_appendingSlices() }
3807+
DataTests.test("test_appendingNonContiguousSequence_exactCount") { TestData().test_appendingNonContiguousSequence_exactCount() }
3808+
DataTests.test("test_appendingNonContiguousSequence_underestimatedCount") { TestData().test_appendingNonContiguousSequence_underestimatedCount() }
37563809
DataTests.test("test_sequenceInitializers") { TestData().test_sequenceInitializers() }
37573810
DataTests.test("test_reversedDataInit") { TestData().test_reversedDataInit() }
37583811
DataTests.test("test_replaceSubrangeReferencingMutable") { TestData().test_replaceSubrangeReferencingMutable() }

0 commit comments

Comments
 (0)