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 {
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 (
Original file line number Diff line number Diff 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+
5988StringTraps . test ( " UTF8ViewSubscript/endIndexSuccessor " )
6089 . skip ( . custom(
6190 { _isFastAssertConfiguration ( ) } ,
You can’t perform that action at this time.
0 commit comments