Skip to content

Commit 3c99689

Browse files
committed
[stdlib] String: Implement happy paths for index validation
1 parent d18b5f5 commit 3c99689

File tree

7 files changed

+389
-231
lines changed

7 files changed

+389
-231
lines changed

stdlib/public/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ set(SWIFTLIB_ESSENTIAL
157157
StringProtocol.swift
158158
StringIndex.swift
159159
StringIndexConversions.swift
160+
StringIndexValidation.swift
160161
StringInterpolation.swift
161162
StringLegacy.swift
162163
StringNormalization.swift

stdlib/public/core/GroupInfo.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"StringHashable.swift",
2727
"StringIndex.swift",
2828
"StringIndexConversions.swift",
29+
"StringIndexValidation.swift",
2930
"StringInterpolation.swift",
3031
"StringLegacy.swift",
3132
"StringNormalization.swift",

stdlib/public/core/StringCharacterView.swift

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ extension String: BidirectionalCollection {
5454
/// `endIndex`.
5555
/// - Returns: The index value immediately after `i`.
5656
public func index(after i: Index) -> Index {
57-
let i = _guts.roundDownToNearestCharacter(_guts.validateScalarIndex(i))
57+
let i = _guts.validateCharacterIndex(i)
5858
return _uncheckedIndex(after: i)
5959
}
6060

@@ -83,8 +83,7 @@ extension String: BidirectionalCollection {
8383
/// `startIndex`.
8484
/// - Returns: The index value immediately before `i`.
8585
public func index(before i: Index) -> Index {
86-
let i = _guts.roundDownToNearestCharacter(
87-
_guts.validateInclusiveScalarIndex(i))
86+
let i = _guts.validateInclusiveCharacterIndex(i)
8887
// Note: Aligning an index may move it closer towards the `startIndex`, so
8988
// the `i > startIndex` check needs to come after rounding.
9089
_precondition(i > startIndex, "String index is out of bounds")
@@ -138,8 +137,7 @@ extension String: BidirectionalCollection {
138137

139138
// TODO: known-ASCII and single-scalar-grapheme fast path, etc.
140139

141-
var i = _guts.roundDownToNearestCharacter(
142-
_guts.validateInclusiveScalarIndex(i))
140+
var i = _guts.validateInclusiveCharacterIndex(i)
143141

144142
if distance >= 0 {
145143
for _ in stride(from: 0, to: distance, by: 1) {
@@ -214,8 +212,7 @@ extension String: BidirectionalCollection {
214212
let limit = _guts.ensureMatchingEncoding(limit)
215213
let start = _guts.ensureMatchingEncoding(i)
216214

217-
var i = _guts.roundDownToNearestCharacter(
218-
_guts.validateInclusiveScalarIndex(i))
215+
var i = _guts.validateInclusiveCharacterIndex(i)
219216

220217
if distance >= 0 {
221218
for _ in stride(from: 0, to: distance, by: 1) {
@@ -253,10 +250,8 @@ extension String: BidirectionalCollection {
253250
// grapheme breaks -- swapping `start` and `end` may change the magnitude of
254251
// the result.
255252

256-
let start = _guts.roundDownToNearestCharacter(
257-
_guts.validateInclusiveScalarIndex(start))
258-
let end = _guts.roundDownToNearestCharacter(
259-
_guts.validateInclusiveScalarIndex(end))
253+
let start = _guts.validateInclusiveCharacterIndex(start)
254+
let end = _guts.validateInclusiveCharacterIndex(end)
260255

261256
// TODO: known-ASCII and single-scalar-grapheme fast path, etc.
262257

stdlib/public/core/StringGuts.swift

Lines changed: 0 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -400,186 +400,6 @@ extension _StringGuts {
400400
}
401401
}
402402

403-
// Index validation
404-
extension _StringGuts {
405-
/// Validate `i` and adjust its position toward the start, returning the
406-
/// resulting index or trapping as appropriate. If this function returns, then
407-
/// the returned value
408-
///
409-
/// - has an encoding that matches this string,
410-
/// - is within the bounds of this string, and
411-
/// - is aligned on a scalar boundary.
412-
@_alwaysEmitIntoClient
413-
internal func validateScalarIndex(_ i: String.Index) -> String.Index {
414-
let i = ensureMatchingEncoding(i)
415-
_precondition(i._encodedOffset < count, "String index is out of bounds")
416-
return scalarAlign(i)
417-
}
418-
419-
/// Validate `i` and adjust its position toward the start, returning the
420-
/// resulting index or trapping as appropriate. If this function returns, then
421-
/// the returned value
422-
///
423-
/// - has an encoding that matches this string,
424-
/// - is within `start ..< end`, and
425-
/// - is aligned on a scalar boundary.
426-
@_alwaysEmitIntoClient
427-
internal func validateScalarIndex(
428-
_ i: String.Index,
429-
in bounds: Range<String.Index>
430-
) -> String.Index {
431-
_internalInvariant(bounds.upperBound <= endIndex)
432-
433-
let i = ensureMatchingEncoding(i)
434-
_precondition(i >= bounds.lowerBound && i < bounds.upperBound,
435-
"Substring index is out of bounds")
436-
return scalarAlign(i)
437-
}
438-
}
439-
440-
extension _StringGuts {
441-
/// Validate `i` and adjust its position toward the start, returning the
442-
/// resulting index or trapping as appropriate. If this function returns, then
443-
/// the returned value
444-
///
445-
/// - has an encoding that matches this string,
446-
/// - is within the bounds of this string (including the `endIndex`), and
447-
/// - is aligned on a scalar boundary.
448-
@_alwaysEmitIntoClient
449-
internal func validateInclusiveScalarIndex(
450-
_ i: String.Index
451-
) -> String.Index {
452-
let i = ensureMatchingEncoding(i)
453-
_precondition(i._encodedOffset <= count, "String index is out of bounds")
454-
return scalarAlign(i)
455-
}
456-
457-
/// Validate `i` and adjust its position toward the start, returning the
458-
/// resulting index or trapping as appropriate. If this function returns, then
459-
/// the returned value
460-
///
461-
/// - has an encoding that matches this string,
462-
/// - is within the bounds of this string (including the `endIndex`), and
463-
/// - is aligned on a scalar boundary.
464-
internal func validateInclusiveScalarIndex(
465-
_ i: String.Index,
466-
in bounds: Range<String.Index>
467-
) -> String.Index {
468-
_internalInvariant(bounds.upperBound <= endIndex)
469-
470-
let i = ensureMatchingEncoding(i)
471-
_precondition(i >= bounds.lowerBound && i <= bounds.upperBound,
472-
"Substring index is out of bounds")
473-
return scalarAlign(i)
474-
}
475-
}
476-
477-
extension _StringGuts {
478-
@_alwaysEmitIntoClient
479-
internal func validateSubscalarRange(
480-
_ range: Range<String.Index>
481-
) -> Range<String.Index> {
482-
let upper = ensureMatchingEncoding(range.upperBound)
483-
let lower = ensureMatchingEncoding(range.lowerBound)
484-
485-
// Note: if only `lower` was miscoded, then the range invariant `lower <=
486-
// upper` may no longer hold after the above conversions, so we need to
487-
// re-check it here.
488-
_precondition(upper._encodedOffset <= count && lower <= upper,
489-
"String index range is out of bounds")
490-
491-
return Range(_uncheckedBounds: (lower, upper))
492-
}
493-
494-
@_alwaysEmitIntoClient
495-
internal func validateSubscalarRange(
496-
_ range: Range<String.Index>,
497-
in bounds: Range<String.Index>
498-
) -> Range<String.Index> {
499-
_internalInvariant(bounds.upperBound <= endIndex)
500-
501-
let upper = ensureMatchingEncoding(range.upperBound)
502-
let lower = ensureMatchingEncoding(range.lowerBound)
503-
504-
// Note: if only `lower` was miscoded, then the range invariant `lower <=
505-
// upper` may no longer hold after the above conversions, so we need to
506-
// re-check it here.
507-
_precondition(
508-
upper <= bounds.upperBound
509-
&& lower >= bounds.lowerBound
510-
&& lower <= upper,
511-
"Substring index range is out of bounds")
512-
513-
return Range(_uncheckedBounds: (lower, upper))
514-
}
515-
}
516-
517-
extension _StringGuts {
518-
/// Validate `range` and adjust the position of its bounds, returning the
519-
/// resulting range or trapping as appropriate. If this function returns, then
520-
/// the bounds of the returned value
521-
///
522-
/// - have an encoding that matches this string,
523-
/// - are within the bounds of this string, and
524-
/// - are aligned on a scalar boundary.
525-
internal func validateScalarRange(
526-
_ range: Range<String.Index>
527-
) -> Range<String.Index> {
528-
var upper = ensureMatchingEncoding(range.upperBound)
529-
var lower = ensureMatchingEncoding(range.lowerBound)
530-
531-
// Note: if only `lower` was miscoded, then the range invariant `lower <=
532-
// upper` may no longer hold after the above conversions, so we need to
533-
// re-check it here.
534-
_precondition(upper._encodedOffset <= count && lower <= upper,
535-
"String index range is out of bounds")
536-
537-
upper = scalarAlign(upper)
538-
lower = scalarAlign(lower)
539-
540-
// Older binaries may generate `startIndex` without the
541-
// `_isCharacterAligned` flag. Compensate for that here so that substrings
542-
// that start at the beginning will never get the sad path in
543-
// `index(after:)`. Note that we don't need to do this for `upper` and we
544-
// don't need to compare against the `endIndex` -- those aren't nearly as
545-
// critical.
546-
if lower._encodedOffset == 0 { lower = lower._characterAligned }
547-
548-
return Range(_uncheckedBounds: (lower, upper))
549-
}
550-
551-
/// Validate `range` and adjust the position of its bounds, returning the
552-
/// resulting range or trapping as appropriate. If this function returns, then
553-
/// the bounds of the returned value
554-
///
555-
/// - have an encoding that matches this string,
556-
/// - are within `start ..< end`, and
557-
/// - are aligned on a scalar boundary.
558-
internal func validateScalarRange(
559-
_ range: Range<String.Index>,
560-
in bounds: Range<String.Index>
561-
) -> Range<String.Index> {
562-
_internalInvariant(bounds.upperBound <= endIndex)
563-
564-
var upper = ensureMatchingEncoding(range.upperBound)
565-
var lower = ensureMatchingEncoding(range.lowerBound)
566-
567-
// Note: if only `lower` was miscoded, then the range invariant `lower <=
568-
// upper` may no longer hold after the above conversions, so we need to
569-
// re-check it here.
570-
_precondition(
571-
upper <= bounds.upperBound
572-
&& lower >= bounds.lowerBound
573-
&& lower <= upper,
574-
"Substring index range is out of bounds")
575-
576-
upper = scalarAlign(upper)
577-
lower = scalarAlign(lower)
578-
579-
return Range(_uncheckedBounds: (lower, upper))
580-
}
581-
}
582-
583403
// Old SPI(corelibs-foundation)
584404
extension _StringGuts {
585405
@available(*, deprecated)

stdlib/public/core/StringIndex.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,18 @@ extension String.Index {
479479
}
480480
}
481481

482+
extension String.Index {
483+
@_alwaysEmitIntoClient @inline(__always) // Swift 5.7
484+
internal var _isUTF8CharacterIndex: Bool {
485+
_canBeUTF8 && _isCharacterAligned
486+
}
487+
488+
@_alwaysEmitIntoClient @inline(__always) // Swift 5.7
489+
internal var _isUTF8ScalarIndex: Bool {
490+
_canBeUTF8 && _isScalarAligned
491+
}
492+
}
493+
482494
extension String.Index: Equatable {
483495
@inlinable @inline(__always)
484496
public static func == (lhs: String.Index, rhs: String.Index) -> Bool {

0 commit comments

Comments
 (0)