Skip to content

Commit 6c992d9

Browse files
committed
Return nil on applying() failure instead of crashing (rdar://problem/53663769)
1 parent d86fec9 commit 6c992d9

File tree

2 files changed

+98
-23
lines changed

2 files changed

+98
-23
lines changed

stdlib/public/core/Diffing.swift

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
1616
extension CollectionDifference {
1717
fileprivate func _fastEnumeratedApply(
18-
_ consume: (Change) -> Void
19-
) {
18+
_ consume: (Change) throws -> Void
19+
) rethrows {
2020
let totalRemoves = removals.count
2121
let totalInserts = insertions.count
2222
var enumeratedRemoves = 0
@@ -41,7 +41,7 @@ extension CollectionDifference {
4141
preconditionFailure()
4242
}
4343

44-
consume(change)
44+
try consume(change)
4545

4646
switch change {
4747
case .remove(_, _, _):
@@ -53,6 +53,9 @@ extension CollectionDifference {
5353
}
5454
}
5555

56+
// Error type allows the use of throw to unroll state on application failure
57+
fileprivate enum _ApplicationError : Error { case failed }
58+
5659
extension RangeReplaceableCollection {
5760
/// Applies the given difference to this collection.
5861
///
@@ -76,34 +79,44 @@ extension RangeReplaceableCollection {
7679
into target: inout Self,
7780
contentsOf source: Self,
7881
from index: inout Self.Index, count: Int
79-
) {
82+
) throws {
8083
let start = index
81-
source.formIndex(&index, offsetBy: count)
84+
if !source.formIndex(&index, offsetBy: count, limitedBy: source.endIndex) {
85+
throw _ApplicationError.failed
86+
}
8287
target.append(contentsOf: source[start..<index])
8388
}
8489

85-
difference._fastEnumeratedApply { change in
86-
switch change {
87-
case .remove(offset: let offset, element: _, associatedWith: _):
88-
let origCount = offset - enumeratedOriginals
89-
append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
90-
enumeratedOriginals += origCount + 1 // Removal consumes an original element
91-
currentIndex = self.index(after: currentIndex)
92-
enumeratedRemoves += 1
93-
case .insert(offset: let offset, element: let element, associatedWith: _):
94-
let origCount = (offset + enumeratedRemoves - enumeratedInserts) - enumeratedOriginals
95-
append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
96-
result.append(element)
97-
enumeratedOriginals += origCount
98-
enumeratedInserts += 1
90+
do {
91+
try difference._fastEnumeratedApply { change in
92+
switch change {
93+
case .remove(offset: let offset, element: _, associatedWith: _):
94+
let origCount = offset - enumeratedOriginals
95+
try append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
96+
if currentIndex == self.endIndex {
97+
// Removing nonexistent element off the end of the collection
98+
throw _ApplicationError.failed
99+
}
100+
enumeratedOriginals += origCount + 1 // Removal consumes an original element
101+
currentIndex = self.index(after: currentIndex)
102+
enumeratedRemoves += 1
103+
case .insert(offset: let offset, element: let element, associatedWith: _):
104+
let origCount = (offset + enumeratedRemoves - enumeratedInserts) - enumeratedOriginals
105+
try append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
106+
result.append(element)
107+
enumeratedOriginals += origCount
108+
enumeratedInserts += 1
109+
}
110+
_internalInvariant(enumeratedOriginals <= self.count)
99111
}
100-
_internalInvariant(enumeratedOriginals <= self.count)
112+
let origCount = self.count - enumeratedOriginals
113+
try append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
114+
_internalInvariant(enumeratedOriginals + origCount == self.count)
115+
} catch {
116+
return nil
101117
}
102-
let origCount = self.count - enumeratedOriginals
103-
append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
104118

105119
_internalInvariant(currentIndex == self.endIndex)
106-
_internalInvariant(enumeratedOriginals + origCount == self.count)
107120
_internalInvariant(result.count == self.count + enumeratedInserts - enumeratedRemoves)
108121
return result
109122
}

test/stdlib/Diffing.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,68 @@ if #available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) {
629629
}}}}}}
630630
}
631631

632+
suite.test("Fast applicator error condition") {
633+
let bear = "bear"
634+
let bird = "bird"
635+
let bat = "bat"
636+
637+
let diff = bird.difference(from: bear)
638+
639+
expectEqual(nil, bat.applying(diff))
640+
}
641+
642+
suite.test("Fast applicator boundary condition remove last element") {
643+
let base = [1, 2, 3]
644+
645+
expectEqual([1, 2], base.applying(CollectionDifference<Int>([.remove(offset: base.count - 1, element: 3, associatedWith: nil)])!))
646+
}
647+
648+
suite.test("Fast applicator boundary condition append element") {
649+
let base = [1, 2, 3]
650+
651+
expectEqual([1, 2, 3, 4], base.applying(CollectionDifference<Int>([.insert(offset: base.count, element: 4, associatedWith: nil)])!))
652+
}
653+
654+
suite.test("Fast applicator error boundary condition remove at endIndex") {
655+
let base = [1, 2, 3]
656+
657+
expectEqual(nil, base.applying(CollectionDifference<Int>([.remove(offset: base.count, element: 4, associatedWith: nil)])!))
658+
}
659+
660+
suite.test("Fast applicator error boundary condition insert beyond end") {
661+
let base = [1, 2, 3]
662+
663+
expectEqual(nil, base.applying(CollectionDifference<Int>([.insert(offset: base.count + 1, element: 5, associatedWith: nil)])!))
664+
}
665+
666+
suite.test("Fast applicator boundary condition replace tail") {
667+
let base = [1, 2, 3]
668+
669+
expectEqual([1, 2, 4], base.applying(CollectionDifference<Int>([
670+
.remove(offset: base.count - 1, element: 3, associatedWith: nil),
671+
.insert(offset: base.count - 1, element: 4, associatedWith: nil)
672+
])!))
673+
}
674+
675+
suite.test("Fast applicator error boundary condition insert beyond end after tail removal") {
676+
let base = [1, 2, 3]
677+
678+
expectEqual(nil, base.applying(CollectionDifference<Int>([
679+
.remove(offset: base.count - 1, element: 3, associatedWith: nil),
680+
.insert(offset: base.count, element: 4, associatedWith: nil)
681+
])!))
682+
683+
}
684+
685+
suite.test("Fast applicator error boundary condition insert beyond end after non-tail removal") {
686+
let base = [1, 2, 3]
687+
688+
expectEqual(nil, base.applying(CollectionDifference<Int>([
689+
.remove(offset: base.count - 2, element: 2, associatedWith: nil),
690+
.insert(offset: base.count, element: 4, associatedWith: nil)
691+
])!))
692+
}
693+
632694
suite.test("Fast applicator fuzzer") {
633695
func makeArray() -> [Int] {
634696
var arr = [Int]()

0 commit comments

Comments
 (0)