File tree Expand file tree Collapse file tree 2 files changed +37
-2
lines changed Expand file tree Collapse file tree 2 files changed +37
-2
lines changed Original file line number Diff line number Diff line change @@ -68,10 +68,16 @@ extension String: BidirectionalCollection {
68
68
/// `startIndex`.
69
69
/// - Returns: The index value immediately before `i`.
70
70
public func index( before i: Index ) -> Index {
71
- _precondition ( i > startIndex, " String index is out of bounds " )
72
-
73
71
// 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 " )
74
78
let i = _guts. scalarAlign ( i)
79
+ _precondition ( i > startIndex, " String index is out of bounds " )
80
+
75
81
let stride = _characterStride ( endingAt: i)
76
82
let priorOffset = i. _encodedOffset &- stride
77
83
return Index (
Original file line number Diff line number Diff line change @@ -56,6 +56,35 @@ StringTraps.test("subscript(_:)/endIndex")
56
56
_ = s [ i]
57
57
}
58
58
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
+
59
88
StringTraps . test ( " UTF8ViewSubscript/endIndexSuccessor " )
60
89
. skip ( . custom(
61
90
{ _isFastAssertConfiguration ( ) } ,
You can’t perform that action at this time.
0 commit comments