//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // Index validation extension _StringGuts { @_alwaysEmitIntoClient @inline(__always) internal func isFastScalarIndex(_ i: String.Index) -> Bool { hasMatchingEncoding(i) && i._isScalarAligned } @_alwaysEmitIntoClient @inline(__always) internal func isFastCharacterIndex(_ i: String.Index) -> Bool { hasMatchingEncoding(i) && i._isCharacterAligned } } // Subscalar index validation (UTF-8 & UTF-16 views) extension _StringGuts { @_alwaysEmitIntoClient internal func validateSubscalarIndex(_ i: String.Index) -> String.Index { let i = ensureMatchingEncoding(i) _precondition(i._encodedOffset < count, "String index is out of bounds") return i } @_alwaysEmitIntoClient internal func validateSubscalarIndex( _ i: String.Index, in bounds: Range ) -> String.Index { _internalInvariant(bounds.upperBound <= endIndex) let i = ensureMatchingEncoding(i) _precondition(i >= bounds.lowerBound && i < bounds.upperBound, "Substring index is out of bounds") return i } @_alwaysEmitIntoClient internal func validateInclusiveSubscalarIndex( _ i: String.Index ) -> String.Index { let i = ensureMatchingEncoding(i) _precondition(i._encodedOffset <= count, "String index is out of bounds") return i } @_alwaysEmitIntoClient internal func validateInclusiveSubscalarIndex( _ i: String.Index, in bounds: Range ) -> String.Index { _internalInvariant(bounds.upperBound <= endIndex) let i = ensureMatchingEncoding(i) _precondition(i >= bounds.lowerBound && i <= bounds.upperBound, "Substring index is out of bounds") return i } @_alwaysEmitIntoClient internal func validateSubscalarRange( _ range: Range ) -> Range { let upper = ensureMatchingEncoding(range.upperBound) let lower = ensureMatchingEncoding(range.lowerBound) // Note: if only `lower` was miscoded, then the range invariant `lower <= // upper` may no longer hold after the above conversions, so we need to // re-check it here. _precondition(upper <= endIndex && lower <= upper, "String index range is out of bounds") return unsafe Range(_uncheckedBounds: (lower, upper)) } @_alwaysEmitIntoClient internal func validateSubscalarRange( _ range: Range, in bounds: Range ) -> Range { _internalInvariant(bounds.upperBound <= endIndex) let upper = ensureMatchingEncoding(range.upperBound) let lower = ensureMatchingEncoding(range.lowerBound) // Note: if only `lower` was miscoded, then the range invariant `lower <= // upper` may no longer hold after the above conversions, so we need to // re-check it here. _precondition( lower >= bounds.lowerBound && lower <= upper && upper <= bounds.upperBound, "Substring index range is out of bounds") return unsafe Range(_uncheckedBounds: (lower, upper)) } } // Scalar index validation (Unicode scalar views) extension _StringGuts { /// Validate `i` and adjust its position toward the start, returning the /// resulting index or trapping as appropriate. If this function returns, then /// the returned value /// /// - has an encoding that matches this string, /// - is within the bounds of this string, and /// - is aligned on a scalar boundary. @_alwaysEmitIntoClient internal func validateScalarIndex(_ i: String.Index) -> String.Index { if isFastScalarIndex(i) { _precondition(i._encodedOffset < count, "String index is out of bounds") return i } return scalarAlign(validateSubscalarIndex(i)) } /// Validate `i` and adjust its position toward the start, returning the /// resulting index or trapping as appropriate. If this function returns, then /// the returned value /// /// - has an encoding that matches this string, /// - is within `start ..< end`, and /// - is aligned on a scalar boundary. @_alwaysEmitIntoClient internal func validateScalarIndex( _ i: String.Index, in bounds: Range ) -> String.Index { _internalInvariant(bounds.upperBound <= endIndex) if isFastScalarIndex(i) { _precondition(i >= bounds.lowerBound && i < bounds.upperBound, "Substring index is out of bounds") return i } return scalarAlign(validateSubscalarIndex(i, in: bounds)) } } extension _StringGuts { /// Validate `i` and adjust its position toward the start, returning the /// resulting index or trapping as appropriate. If this function returns, then /// the returned value /// /// - has an encoding that matches this string, /// - is within the bounds of this string (including the `endIndex`), and /// - is aligned on a scalar boundary. @_alwaysEmitIntoClient internal func validateInclusiveScalarIndex( _ i: String.Index ) -> String.Index { if isFastScalarIndex(i) { _precondition(i._encodedOffset <= count, "String index is out of bounds") return i } return scalarAlign(validateInclusiveSubscalarIndex(i)) } /// Validate `i` and adjust its position toward the start, returning the /// resulting index or trapping as appropriate. If this function returns, then /// the returned value /// /// - has an encoding that matches this string, /// - is within the bounds of this string (including the `endIndex`), and /// - is aligned on a scalar boundary. @_alwaysEmitIntoClient internal func validateInclusiveScalarIndex( _ i: String.Index, in bounds: Range ) -> String.Index { _internalInvariant(bounds.upperBound <= endIndex) if isFastScalarIndex(i) { _precondition(i >= bounds.lowerBound && i <= bounds.upperBound, "Substring index is out of bounds") return i } return scalarAlign(validateInclusiveSubscalarIndex(i, in: bounds)) } } extension _StringGuts { /// Validate `range` and adjust the position of its bounds, returning the /// resulting range or trapping as appropriate. If this function returns, then /// the bounds of the returned value /// /// - have an encoding that matches this string, /// - are within the bounds of this string, and /// - are aligned on a scalar boundary. internal func validateScalarRange( _ range: Range ) -> Range { if isFastScalarIndex(range.lowerBound), isFastScalarIndex(range.upperBound) { _precondition(range.upperBound._encodedOffset <= count, "String index range is out of bounds") return range } let r = validateSubscalarRange(range) return unsafe Range( _uncheckedBounds: (scalarAlign(r.lowerBound), scalarAlign(r.upperBound))) } /// Validate `range` and adjust the position of its bounds, returning the /// resulting range or trapping as appropriate. If this function returns, then /// the bounds of the returned value /// /// - have an encoding that matches this string, /// - are within `start ..< end`, and /// - are aligned on a scalar boundary. internal func validateScalarRange( _ range: Range, in bounds: Range ) -> Range { _internalInvariant(bounds.upperBound <= endIndex) if isFastScalarIndex(range.lowerBound), isFastScalarIndex(range.upperBound) { _precondition( range.lowerBound >= bounds.lowerBound && range.upperBound <= bounds.upperBound, "String index range is out of bounds") return range } let r = validateSubscalarRange(range, in: bounds) let upper = scalarAlign(r.upperBound) let lower = scalarAlign(r.lowerBound) return unsafe Range(_uncheckedBounds: (lower, upper)) } } // Character index validation (String & Substring) extension _StringGuts { internal func validateCharacterIndex(_ i: String.Index) -> String.Index { if isFastCharacterIndex(i) { _precondition(i._encodedOffset < count, "String index is out of bounds") return i } return roundDownToNearestCharacter(scalarAlign(validateSubscalarIndex(i))) } internal func validateCharacterIndex( _ i: String.Index, in bounds: Range ) -> String.Index { _internalInvariant(bounds.upperBound <= endIndex) if isFastCharacterIndex(i) { _precondition(i >= bounds.lowerBound && i < bounds.upperBound, "Substring index is out of bounds") return i } return roundDownToNearestCharacter( scalarAlign(validateSubscalarIndex(i, in: bounds)), in: bounds) } internal func validateInclusiveCharacterIndex( _ i: String.Index ) -> String.Index { if isFastCharacterIndex(i) { _precondition(i._encodedOffset <= count, "String index is out of bounds") return i } return roundDownToNearestCharacter( scalarAlign(validateInclusiveSubscalarIndex(i))) } internal func validateInclusiveCharacterIndex( _ i: String.Index, in bounds: Range ) -> String.Index { _internalInvariant(bounds.upperBound <= endIndex) if isFastCharacterIndex(i) { _precondition(i >= bounds.lowerBound && i <= bounds.upperBound, "Substring index is out of bounds") return i } return roundDownToNearestCharacter( scalarAlign(validateInclusiveSubscalarIndex(i, in: bounds)), in: bounds) } } // Temporary additions to deal with binary compatibility issues with existing // binaries that accidentally pass invalid indices to String APIs in cases that // were previously undiagnosed. // // FIXME: Remove these after a release or two. extension _StringGuts { /// A version of `validateInclusiveSubscalarIndex` that only traps if the main /// executable was linked with Swift Stdlib version 5.7 or better. This is /// used to work around binary compatibility problems with existing apps that /// pass invalid indices to String APIs. internal func validateInclusiveSubscalarIndex_5_7( _ i: String.Index ) -> String.Index { let i = ensureMatchingEncoding(i) _precondition( ifLinkedOnOrAfter: .v5_7_0, i._encodedOffset <= count, "String index is out of bounds") return i } /// A version of `validateInclusiveScalarIndex` that only traps if the main /// executable was linked with Swift Stdlib version 5.7 or better. This is /// used to work around binary compatibility problems with existing apps that /// pass invalid indices to String APIs. internal func validateInclusiveScalarIndex_5_7( _ i: String.Index ) -> String.Index { if isFastScalarIndex(i) { _precondition( ifLinkedOnOrAfter: .v5_7_0, i._encodedOffset <= count, "String index is out of bounds") return i } return scalarAlign(validateInclusiveSubscalarIndex_5_7(i)) } /// A version of `validateSubscalarRange` that only traps if the main /// executable was linked with Swift Stdlib version 5.7 or better. This is /// used to work around binary compatibility problems with existing apps that /// pass invalid indices to String APIs. internal func validateSubscalarRange_5_7( _ range: Range ) -> Range { let upper = ensureMatchingEncoding(range.upperBound) let lower = ensureMatchingEncoding(range.lowerBound) _precondition(upper <= endIndex && lower <= upper, "String index range is out of bounds") return unsafe Range(_uncheckedBounds: (lower, upper)) } /// A version of `validateScalarRange` that only traps if the main executable /// was linked with Swift Stdlib version 5.7 or better. This is used to work /// around binary compatibility problems with existing apps that pass invalid /// indices to String APIs. internal func validateScalarRange_5_7( _ range: Range ) -> Range { if isFastScalarIndex(range.lowerBound), isFastScalarIndex(range.upperBound) { _precondition( ifLinkedOnOrAfter: .v5_7_0, range.upperBound._encodedOffset <= count, "String index range is out of bounds") return range } let r = validateSubscalarRange_5_7(range) return unsafe Range( _uncheckedBounds: (scalarAlign(r.lowerBound), scalarAlign(r.upperBound))) } /// A version of `validateInclusiveCharacterIndex` that only traps if the main /// executable was linked with Swift Stdlib version 5.7 or better. This is /// used to work around binary compatibility problems with existing apps that /// pass invalid indices to String APIs. internal func validateInclusiveCharacterIndex_5_7( _ i: String.Index ) -> String.Index { if isFastCharacterIndex(i) { _precondition( ifLinkedOnOrAfter: .v5_7_0, i._encodedOffset <= count, "String index is out of bounds") return i } return roundDownToNearestCharacter( scalarAlign(validateInclusiveSubscalarIndex_5_7(i))) } }