Skip to content

Commit f555464

Browse files
authored
Merge pull request #1995 from itaiferber/data-underestimated-sequence-initializer-fix
Fix off-by-one when initializing Data with discontiguous, underestimated Sequences
2 parents 67e375f + 4125270 commit f555464

File tree

2 files changed

+55
-6
lines changed

2 files changed

+55
-6
lines changed

Foundation/Data.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2085,9 +2085,8 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
20852085
// ... and append the rest byte-wise, buffering through an InlineData.
20862086
var buffer = InlineData()
20872087
while let element = iter.next() {
2088-
if buffer.count < buffer.capacity {
2089-
buffer.append(byte: element)
2090-
} else {
2088+
buffer.append(byte: element)
2089+
if buffer.count == buffer.capacity {
20912090
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
20922091
buffer.count = 0
20932092
}
@@ -2379,9 +2378,8 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
23792378
// ... and append the rest byte-wise, buffering through an InlineData.
23802379
var buffer = InlineData()
23812380
while let element = iter.next() {
2382-
if buffer.count < buffer.capacity {
2383-
buffer.append(byte: element)
2384-
} else {
2381+
buffer.append(byte: element)
2382+
if buffer.count == buffer.capacity {
23852383
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
23862384
buffer.count = 0
23872385
}

TestFoundation/TestNSData.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,8 @@ class TestNSData: LoopbackServerTest {
521521
("test_copyBytes2", test_copyBytes2),
522522
("test_sliceOfSliceViaRangeExpression", test_sliceOfSliceViaRangeExpression),
523523
("test_appendingSlices", test_appendingSlices),
524+
("test_appendingNonContiguousSequence_exactCount", test_appendingNonContiguousSequence_exactCount),
525+
("test_appendingNonContiguousSequence_underestimatedCount", test_appendingNonContiguousSequence_underestimatedCount),
524526
("test_sequenceInitializers", test_sequenceInitializers),
525527
("test_reversedDataInit", test_reversedDataInit),
526528
("test_replaceSubrangeReferencingMutable", test_replaceSubrangeReferencingMutable),
@@ -4391,6 +4393,55 @@ extension TestNSData {
43914393
d2.append(slice)
43924394
XCTAssertEqual(Data([1]), slice)
43934395
}
4396+
4397+
// This test uses `repeatElement` to produce a sequence -- the produced sequence reports its actual count as its `.underestimatedCount`.
4398+
func test_appendingNonContiguousSequence_exactCount() {
4399+
var d = Data()
4400+
4401+
// d should go from .empty representation to .inline.
4402+
// Appending a small enough sequence to fit in .inline should actually be copied.
4403+
d.append(contentsOf: 0x00...0x01)
4404+
expectEqual(Data([0x00, 0x01]), d)
4405+
4406+
// Appending another small sequence should similarly still work.
4407+
d.append(contentsOf: 0x02...0x02)
4408+
expectEqual(Data([0x00, 0x01, 0x02]), d)
4409+
4410+
// If we append a sequence of elements larger than a single InlineData, the internal append here should buffer.
4411+
// We want to make sure that buffering in this way does not accidentally drop trailing elements on the floor.
4412+
d.append(contentsOf: 0x03...0x2F)
4413+
expectEqual(Data([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
4414+
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
4415+
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
4416+
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
4417+
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
4418+
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F]), d)
4419+
}
4420+
4421+
// This test is like test_appendingNonContiguousSequence_exactCount but uses a sequence which reports 0 for its `.underestimatedCount`.
4422+
// This attempts to hit the worst-case scenario of `Data.append<S>(_:)` -- a discontiguous sequence of unknown length.
4423+
func test_appendingNonContiguousSequence_underestimatedCount() {
4424+
var d = Data()
4425+
4426+
// d should go from .empty representation to .inline.
4427+
// Appending a small enough sequence to fit in .inline should actually be copied.
4428+
d.append(contentsOf: (0x00...0x01).makeIterator()) // `.makeIterator()` produces a sequence whose `.underestimatedCount` is 0.
4429+
expectEqual(Data([0x00, 0x01]), d)
4430+
4431+
// Appending another small sequence should similarly still work.
4432+
d.append(contentsOf: (0x02...0x02).makeIterator()) // `.makeIterator()` produces a sequence whose `.underestimatedCount` is 0.
4433+
expectEqual(Data([0x00, 0x01, 0x02]), d)
4434+
4435+
// If we append a sequence of elements larger than a single InlineData, the internal append here should buffer.
4436+
// We want to make sure that buffering in this way does not accidentally drop trailing elements on the floor.
4437+
d.append(contentsOf: (0x03...0x2F).makeIterator()) // `.makeIterator()` produces a sequence whose `.underestimatedCount` is 0.
4438+
expectEqual(Data([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
4439+
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
4440+
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
4441+
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
4442+
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
4443+
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F]), d)
4444+
}
43944445

43954446
func test_sequenceInitializers() {
43964447
let seq = repeatElement(UInt8(0x02), count: 3) // ensure we fall into the sequence case

0 commit comments

Comments
 (0)