Skip to content

Commit fee2787

Browse files
committed
[String] Invalidate breadcrumbs on mutation.
1 parent ec6729a commit fee2787

File tree

4 files changed

+26
-9
lines changed

4 files changed

+26
-9
lines changed

stdlib/public/core/StringBreadcrumbs.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ internal final class _StringBreadcrumbs {
5757
self.utf16Length = i
5858
_sanityCheck(self.crumbs.count == 1 + (self.utf16Length / stride))
5959

60-
_invariantCheck()
60+
_invariantCheck(for: str)
6161
}
6262
}
6363

@@ -97,10 +97,13 @@ extension _StringBreadcrumbs {
9797
}
9898

9999
#if !INTERNAL_CHECKS_ENABLED
100-
@nonobjc @inline(__always) internal func _invariantCheck() {}
100+
@nonobjc @inline(__always) internal func _invariantCheck(for str: String) {}
101101
#else
102102
@nonobjc @inline(never) @_effects(releasenone)
103-
internal func _invariantCheck() {
103+
internal func _invariantCheck(for str: String) {
104+
_sanityCheck(self.utf16Length == str.utf16._distance(
105+
from: str.startIndex, to: str.endIndex),
106+
"Stale breadcrumbs")
104107
}
105108
#endif // INTERNAL_CHECKS_ENABLED
106109
}

stdlib/public/core/StringObject.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,7 +1272,6 @@ extension _StringObject {
12721272
} else {
12731273
_sanityCheck(isLarge)
12741274
_sanityCheck(largeCount == count)
1275-
_countAndFlags._invariantCheck()
12761275
if providesFastUTF8 && largeFastIsNative {
12771276
_sanityCheck(!isSmall)
12781277
_sanityCheck(!largeIsCocoa)
@@ -1284,7 +1283,6 @@ extension _StringObject {
12841283
_sanityCheck(hasNativeStorage)
12851284
_sanityCheck(hasObjCBridgeableObject)
12861285
_sanityCheck(nativeStorage.count == self.count)
1287-
nativeStorage._invariantCheck()
12881286
}
12891287
}
12901288
if largeIsCocoa {

stdlib/public/core/StringStorage.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ extension _StringStorage {
350350
_sanityCheck(_allASCII(self.codeUnits))
351351
}
352352
if let crumbs = _breadcrumbsAddress.pointee {
353-
crumbs._invariantCheck()
353+
crumbs._invariantCheck(for: self.asString)
354354
}
355355
}
356356
#endif // INTERNAL_CHECKS_ENABLED
@@ -369,6 +369,9 @@ extension _StringStorage {
369369
count: newCount, isASCII: newIsASCII)
370370
#endif
371371
self.terminator.pointee = 0
372+
373+
// TODO(String performance): Consider updating breadcrumbs when feasible
374+
self._breadcrumbsAddress.pointee = nil
372375
_invariantCheck()
373376
}
374377

@@ -552,7 +555,7 @@ extension _SharedStringStorage {
552555
#else
553556
internal func _invariantCheck() {
554557
if let crumbs = _breadcrumbs {
555-
crumbs._invariantCheck()
558+
crumbs._invariantCheck(for: self.asString)
556559
}
557560
_countAndFlags._invariantCheck()
558561
}

validation-test/stdlib/StringBreadcrumbs.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ let largeUnicode = "abéÏ012345678901234567890𓀀"
1515
let emoji = "😀😃🤢🤮👩🏿‍🎤🧛🏻‍♂️🧛🏻‍♂️👩‍👩‍👦‍👦"
1616
let chinese = "Swift 是面向 Apple 平台的编程语言,功能强大且直观易用,而本次更新对其进行了全面优化。"
1717

18+
let nonBMP = String(repeating: "𓀀", count: 1 + (64 / 2))
19+
1820
let largeString: String = {
1921
var result = ""
2022
result += smallASCII
@@ -74,7 +76,6 @@ StringBreadcrumbsTests.test("largeString") {
7476
// Test various boundary conditions with surrogate pairs aligning or not
7577
// aligning
7678
StringBreadcrumbsTests.test("surrogates-heavy") {
77-
let nonBMP = String(repeating: "𓀀", count: 1 + (64 / 2))
7879

7980
// Mis-align the hieroglyphics by 1,2,3 UTF-8 and UTF-16 code units
8081
validateBreadcrumbs(nonBMP)
@@ -86,6 +87,18 @@ StringBreadcrumbsTests.test("surrogates-heavy") {
8687
validateBreadcrumbs("" + nonBMP)
8788
}
8889

89-
// TODO(String testing): test breadcrumb validity after mutation
90+
// Test bread-crumb invalidation
91+
StringBreadcrumbsTests.test("stale breadcrumbs") {
92+
var str = nonBMP + "𓀀"
93+
let oldLen = str.utf16.count
94+
str.removeLast()
95+
expectEqual(oldLen - 2, str.utf16.count)
96+
str += "a"
97+
expectEqual(oldLen - 1, str.utf16.count)
98+
str += "𓀀"
99+
expectEqual(oldLen + 1, str.utf16.count)
100+
}
101+
102+
90103

91104
runAllTests()

0 commit comments

Comments
 (0)