Skip to content

Commit 20c293e

Browse files
authored
Merge pull request #41598 from lorentey/string-index-validation
[stdlib] String.index(before:): Fix index validation issues
2 parents ad90309 + f528db5 commit 20c293e

File tree

2 files changed

+37
-2
lines changed

2 files changed

+37
-2
lines changed

stdlib/public/core/StringCharacterView.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,16 @@ extension String: BidirectionalCollection {
6868
/// `startIndex`.
6969
/// - Returns: The index value immediately before `i`.
7070
public func index(before i: Index) -> Index {
71-
_precondition(i > startIndex, "String index is out of bounds")
72-
7371
// TODO: known-ASCII fast path, single-scalar-grapheme fast path, etc.
72+
73+
// Note: bounds checking in `index(before:)` is tricky as scalar aligning an
74+
// index may need to access storage, but it may also move it closer towards
75+
// the `startIndex`. Therefore, we must check against the `endIndex` before
76+
// aligning, but we need to delay the `i > startIndex` check until after.
77+
_precondition(i <= endIndex, "String index is out of bounds")
7478
let i = _guts.scalarAlign(i)
79+
_precondition(i > startIndex, "String index is out of bounds")
80+
7581
let stride = _characterStride(endingAt: i)
7682
let priorOffset = i._encodedOffset &- stride
7783
return Index(

test/stdlib/StringTraps.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,35 @@ StringTraps.test("subscript(_:)/endIndex")
5656
_ = s[i]
5757
}
5858

59+
StringTraps.test("String.index(before:) trap on i > endIndex")
60+
.skip(
61+
.custom({ _isFastAssertConfiguration() },
62+
reason: "trap is not guaranteed to happen in -Ounchecked"))
63+
.code {
64+
guard #available(SwiftStdlib 5.7, *) else { return }
65+
66+
let long = String(repeating: "X", count: 1024)
67+
let short = "This is a short string"
68+
expectCrashLater()
69+
let i = short.index(before: long.endIndex)
70+
print(i)
71+
}
72+
73+
StringTraps.test("String.index(before:) trap on i == startIndex after scalar alignment")
74+
.skip(
75+
.custom({ _isFastAssertConfiguration() },
76+
reason: "trap is not guaranteed to happen in -Ounchecked"))
77+
.code {
78+
guard #available(SwiftStdlib 5.7, *) else { return }
79+
80+
let s = "🥯 Bagel with schmear"
81+
let i = s.utf8.index(after: s.utf8.startIndex)
82+
expectCrashLater()
83+
// `i` is equivalent to `s.startIndex` as far as `String` is concerned
84+
let j = s.index(before: i)
85+
print(j)
86+
}
87+
5988
StringTraps.test("UTF8ViewSubscript/endIndexSuccessor")
6089
.skip(.custom(
6190
{ _isFastAssertConfiguration() },

0 commit comments

Comments
 (0)