mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[stdlib] Work around binary compatibility issues with String index validation fixes in 5.7
Swift 5.7 added stronger index validation for `String`, so some illegal cases that previously triggered inconsistently diagnosed out of bounds accesses now result in reliable runtime errors. Similarly, attempts at applying an index originally vended by a UTF-8 string on a UTF-16 string now result in a reliable runtime error. As is usually the case, new traps to the stdlib exposes code that contains previously undiagnosed / unreliably diagnosed coding issues. Allow invalid code in binaries built with earlier versions of the stdlib to continue running with the 5.7 library by disabling some of the new traps based on the version of Swift the binary was built with. In the case of an index encoding mismatch, allow transcoding of string storage regardless of the direction of the mismatch. (Previously we only allowed transcoding a UTF-8 string to UTF-16.) rdar://93379333
This commit is contained in:
@@ -116,6 +116,20 @@ SWIFT_RUNTIME_STDLIB_SPI
|
||||
__swift_bool _swift_stdlib_getCurrentStackBounds(__swift_uintptr_t *outBegin,
|
||||
__swift_uintptr_t *outEnd);
|
||||
|
||||
/// A value representing a version number for the Standard Library.
|
||||
typedef struct {
|
||||
__swift_uint32_t _value;
|
||||
} _SwiftStdlibVersion;
|
||||
|
||||
/// Checks if the currently running executable was built using a Swift release
|
||||
/// matching or exceeding the specified Standard Library version number. This
|
||||
/// can be used to stage behavioral changes in the Standard Library, preventing
|
||||
/// them from causing compatibility issues with existing binaries.
|
||||
SWIFT_RUNTIME_STDLIB_INTERNAL
|
||||
__swift_bool _swift_stdlib_isExecutableLinkedOnOrAfter(
|
||||
_SwiftStdlibVersion version
|
||||
) __attribute__((const));
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
@@ -325,6 +325,40 @@ internal func _internalInvariant_5_1(
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Library precondition checks with a linked-on-or-after check, allowing the
|
||||
/// addition of new preconditions while maintaining compatibility with older
|
||||
/// binaries.
|
||||
///
|
||||
/// This version of `_precondition` only traps if the condition returns false
|
||||
/// **and** the current executable was built with a Swift Standard Library
|
||||
/// version equal to or greater than the supplied version.
|
||||
@_transparent
|
||||
internal func _precondition(
|
||||
ifLinkedOnOrAfter version: _SwiftStdlibVersion,
|
||||
_ condition: @autoclosure () -> Bool,
|
||||
_ message: StaticString = StaticString(),
|
||||
file: StaticString = #file, line: UInt = #line
|
||||
) {
|
||||
// Delay the linked-on-or-after check until after we know we have a failed
|
||||
// condition, so that we don't slow down the usual case too much.
|
||||
|
||||
// Note: this is an internal function, so `_isDebugAssertConfiguration` is
|
||||
// expected to evaluate (at compile time) to true in production builds of the
|
||||
// stdlib. The other branches are kept in case the stdlib is built with an
|
||||
// unusual configuration.
|
||||
if _isDebugAssertConfiguration() {
|
||||
if _slowPath(!condition()) {
|
||||
guard _isExecutableLinkedOnOrAfter(version) else { return }
|
||||
_assertionFailure("Fatal error", message, file: file, line: line,
|
||||
flags: _fatalErrorFlags())
|
||||
}
|
||||
} else if _isReleaseAssertConfiguration() {
|
||||
let error = (!condition() && _isExecutableLinkedOnOrAfter(version))
|
||||
Builtin.condfail_message(error._value, message.unsafeRawPointer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@usableFromInline @_transparent
|
||||
internal func _internalInvariantFailure(
|
||||
_ message: StaticString = StaticString(),
|
||||
|
||||
@@ -59,3 +59,48 @@ public func _stdlib_isOSVersionAtLeastOrVariantVersionAtLeast(
|
||||
return _stdlib_isOSVersionAtLeast(major, minor, patch)
|
||||
}
|
||||
#endif
|
||||
|
||||
public typealias _SwiftStdlibVersion = SwiftShims._SwiftStdlibVersion
|
||||
|
||||
/// Return true if the main executable was linked with an SDK version
|
||||
/// corresponding to the given Swift Stdlib release, or later. Otherwise, return
|
||||
/// false.
|
||||
///
|
||||
/// This is useful to maintain compatibility with older binaries after a
|
||||
/// behavioral change in the stdlib.
|
||||
///
|
||||
/// This function must not be called from inlinable code.
|
||||
@inline(__always)
|
||||
internal func _isExecutableLinkedOnOrAfter(
|
||||
_ stdlibVersion: _SwiftStdlibVersion
|
||||
) -> Bool {
|
||||
#if SWIFT_RUNTIME_OS_VERSIONING
|
||||
return _swift_stdlib_isExecutableLinkedOnOrAfter(stdlibVersion)
|
||||
#else
|
||||
return true
|
||||
#endif
|
||||
}
|
||||
|
||||
extension _SwiftStdlibVersion {
|
||||
@_alwaysEmitIntoClient
|
||||
public static var v5_6_0: Self { Self(_value: 0x050600) }
|
||||
|
||||
@_alwaysEmitIntoClient
|
||||
public static var v5_7_0: Self { Self(_value: 0x050700) }
|
||||
|
||||
@available(SwiftStdlib 5.7, *)
|
||||
public static var current: Self { .v5_7_0 }
|
||||
}
|
||||
|
||||
@available(SwiftStdlib 5.7, *)
|
||||
extension _SwiftStdlibVersion: CustomStringConvertible {
|
||||
@available(SwiftStdlib 5.7, *)
|
||||
public var description: String {
|
||||
let major = _value >> 16
|
||||
let minor = (_value >> 8) & 0xFF
|
||||
let patch = _value & 0xFF
|
||||
return "\(major).\(minor).\(patch)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -83,10 +83,16 @@ extension String: BidirectionalCollection {
|
||||
/// `startIndex`.
|
||||
/// - Returns: The index value immediately before `i`.
|
||||
public func index(before i: Index) -> Index {
|
||||
let i = _guts.validateInclusiveCharacterIndex(i)
|
||||
// FIXME: This method used to not properly validate indices before 5.7;
|
||||
// temporarily allow older binaries to keep invoking undefined behavior as
|
||||
// before.
|
||||
let i = _guts.validateInclusiveCharacterIndex_5_7(i)
|
||||
|
||||
// Note: Aligning an index may move it closer towards the `startIndex`, so
|
||||
// the `i > startIndex` check needs to come after rounding.
|
||||
_precondition(i > startIndex, "String index is out of bounds")
|
||||
_precondition(
|
||||
ifLinkedOnOrAfter: .v5_7_0,
|
||||
i > startIndex, "String index is out of bounds")
|
||||
|
||||
return _uncheckedIndex(before: i)
|
||||
}
|
||||
@@ -137,7 +143,10 @@ extension String: BidirectionalCollection {
|
||||
|
||||
// TODO: known-ASCII and single-scalar-grapheme fast path, etc.
|
||||
|
||||
var i = _guts.validateInclusiveCharacterIndex(i)
|
||||
// FIXME: This method used to not properly validate indices before 5.7;
|
||||
// temporarily allow older binaries to keep invoking undefined behavior as
|
||||
// before.
|
||||
var i = _guts.validateInclusiveCharacterIndex_5_7(i)
|
||||
|
||||
if distance >= 0 {
|
||||
for _ in stride(from: 0, to: distance, by: 1) {
|
||||
@@ -209,10 +218,14 @@ extension String: BidirectionalCollection {
|
||||
// ensure our behavior exactly matches the documentation above. We do need
|
||||
// to ensure it has a matching encoding, though. The same goes for `start`,
|
||||
// which is used to determine whether the limit applies at all.
|
||||
|
||||
let limit = _guts.ensureMatchingEncoding(limit)
|
||||
let start = _guts.ensureMatchingEncoding(i)
|
||||
|
||||
var i = _guts.validateInclusiveCharacterIndex(i)
|
||||
// FIXME: This method used to not properly validate indices before 5.7;
|
||||
// temporarily allow older binaries to keep invoking undefined behavior as
|
||||
// before.
|
||||
var i = _guts.validateInclusiveCharacterIndex_5_7(i)
|
||||
|
||||
if distance >= 0 {
|
||||
for _ in stride(from: 0, to: distance, by: 1) {
|
||||
@@ -245,8 +258,11 @@ extension String: BidirectionalCollection {
|
||||
// Note: Prior to Swift 5.7, this function used to be inlinable, forwarding
|
||||
// to `BidirectionalCollection._distance(from:to:)`.
|
||||
|
||||
let start = _guts.validateInclusiveCharacterIndex(start)
|
||||
let end = _guts.validateInclusiveCharacterIndex(end)
|
||||
// FIXME: This method used to not properly validate indices before 5.7;
|
||||
// temporarily allow older binaries to keep invoking undefined behavior as
|
||||
// before.
|
||||
let start = _guts.validateInclusiveCharacterIndex_5_7(start)
|
||||
let end = _guts.validateInclusiveCharacterIndex_5_7(end)
|
||||
|
||||
// TODO: known-ASCII and single-scalar-grapheme fast path, etc.
|
||||
|
||||
|
||||
@@ -347,79 +347,55 @@ extension _StringGuts {
|
||||
@inline(__always)
|
||||
internal func ensureMatchingEncoding(_ i: Index) -> Index {
|
||||
if _fastPath(hasMatchingEncoding(i)) { return i }
|
||||
if let i = _slowEnsureMatchingEncoding(i) { return i }
|
||||
// Note that this trap is not guaranteed to trigger when the process
|
||||
// includes client binaries compiled with a previous Swift release.
|
||||
// (`i._canBeUTF16` can sometimes return true in that case even if the index
|
||||
// actually came from an UTF-8 string.) However, the trap will still often
|
||||
// trigger in this case, as long as the index was initialized by code that
|
||||
// was compiled with 5.7+.
|
||||
//
|
||||
// This trap will rarely if ever trigger on OSes that have stdlibs <= 5.6,
|
||||
// because those versions never set the `isKnownUTF16` flag in
|
||||
// `_StringObject`. (The flag may still be set within inlinable code,
|
||||
// though.)
|
||||
_preconditionFailure("Invalid string index")
|
||||
}
|
||||
|
||||
/// Return an index that corresponds to the same position as `i`, but whose
|
||||
/// encoding can be assumed to match that of `self`, returning `nil` if `i`
|
||||
/// has incompatible encoding.
|
||||
///
|
||||
/// If `i` is UTF-8 encoded, but `self` is an UTF-16 string, then return nil.
|
||||
///
|
||||
/// If `i` is UTF-16 encoded, but `self` is an UTF-8 string, then transcode
|
||||
/// `i`'s offset to UTF-8 and return the resulting index. This allows the use
|
||||
/// of indices from a bridged Cocoa string after the string has been converted
|
||||
/// to a native Swift string. (Such indices are technically still considered
|
||||
/// invalid, but we allow this specific case to keep compatibility with
|
||||
/// existing code that assumes otherwise.)
|
||||
///
|
||||
/// Detecting an encoding mismatch isn't always possible -- older binaries did
|
||||
/// not set the flags that this method relies on. However, false positives
|
||||
/// cannot happen: if this method detects a mismatch, then it is guaranteed to
|
||||
/// be a real one.
|
||||
internal func ensureMatchingEncodingNoTrap(_ i: Index) -> Index? {
|
||||
if hasMatchingEncoding(i) { return i }
|
||||
return _slowEnsureMatchingEncoding(i)
|
||||
}
|
||||
|
||||
@_alwaysEmitIntoClient
|
||||
@inline(never)
|
||||
@_effects(releasenone)
|
||||
internal func _slowEnsureMatchingEncoding(_ i: Index) -> Index? {
|
||||
guard isUTF8 else {
|
||||
// Attempt to use an UTF-8 index on a UTF-16 string. Strings don't usually
|
||||
// get converted to UTF-16 storage, so it seems okay to reject this case
|
||||
// -- the index most likely comes from an unrelated string. (This may
|
||||
// still turn out to affect binary compatibility with broken code in
|
||||
// existing binaries running with new stdlibs. If so, we can replace this
|
||||
// with the same transcoding hack as in the UTF-16->8 case below.)
|
||||
return nil
|
||||
}
|
||||
// Attempt to use an UTF-16 index on a UTF-8 string.
|
||||
//
|
||||
// This can happen if `self` was originally verbatim-bridged, and someone
|
||||
// mistakenly attempts to keep using an old index after a mutation. This is
|
||||
// technically an error, but trapping here would trigger a lot of broken
|
||||
// code that previously happened to work "fine" on e.g. ASCII strings.
|
||||
// Instead, attempt to convert the offset to UTF-8 code units by transcoding
|
||||
// the string. This can be slow, but it often results in a usable index,
|
||||
// even if non-ASCII characters are present. (UTF-16 breadcrumbs help reduce
|
||||
// the severity of the slowdown.)
|
||||
internal func _slowEnsureMatchingEncoding(_ i: Index) -> Index {
|
||||
// Attempt to recover from mismatched encodings between a string and its
|
||||
// index.
|
||||
|
||||
// FIXME: Consider emitting a runtime warning here.
|
||||
// FIXME: Consider performing a linked-on-or-after check & trapping if the
|
||||
// client executable was built on some particular future Swift release.
|
||||
let utf16 = String.UTF16View(self)
|
||||
var r = utf16.index(utf16.startIndex, offsetBy: i._encodedOffset)
|
||||
if isUTF8 {
|
||||
// Attempt to use an UTF-16 index on a UTF-8 string.
|
||||
//
|
||||
// This can happen if `self` was originally verbatim-bridged, and someone
|
||||
// mistakenly attempts to keep using an old index after a mutation. This
|
||||
// is technically an error, but trapping here would trigger a lot of
|
||||
// broken code that previously happened to work "fine" on e.g. ASCII
|
||||
// strings. Instead, attempt to convert the offset to UTF-8 code units by
|
||||
// transcoding the string. This can be slow, but it often results in a
|
||||
// usable index, even if non-ASCII characters are present. (UTF-16
|
||||
// breadcrumbs help reduce the severity of the slowdown.)
|
||||
|
||||
// FIXME: Consider emitting a runtime warning here.
|
||||
// FIXME: Consider performing a linked-on-or-after check & trapping if the
|
||||
// client executable was built on some particular future Swift release.
|
||||
let utf16 = String.UTF16View(self)
|
||||
var r = utf16.index(utf16.startIndex, offsetBy: i._encodedOffset)
|
||||
if i.transcodedOffset != 0 {
|
||||
r = r.encoded(offsetBy: i.transcodedOffset)
|
||||
} else {
|
||||
// Preserve alignment bits if possible.
|
||||
r = r._copyingAlignment(from: i)
|
||||
}
|
||||
return r._knownUTF8
|
||||
}
|
||||
|
||||
// Attempt to use an UTF-8 index on a UTF-16 string. This is rarer, but it
|
||||
// can still happen when e.g. people apply an index they got from
|
||||
// `AttributedString` on the original (bridged) string that they constructed
|
||||
// it from.
|
||||
let utf8 = String.UTF8View(self)
|
||||
var r = utf8.index(utf8.startIndex, offsetBy: i._encodedOffset)
|
||||
if i.transcodedOffset != 0 {
|
||||
r = r.encoded(offsetBy: i.transcodedOffset)
|
||||
} else {
|
||||
// Preserve alignment bits if possible.
|
||||
r = r._copyingAlignment(from: i)
|
||||
}
|
||||
return r._knownUTF8
|
||||
return r._knownUTF16
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,15 +51,13 @@ extension String.Index {
|
||||
/// - target: The string referenced by the resulting index.
|
||||
public init?(_ sourcePosition: String.Index, within target: String) {
|
||||
// As a special exception, we allow `sourcePosition` to be an UTF-16 index
|
||||
// when `self` is a UTF-8 string, to preserve compatibility with (broken)
|
||||
// code that keeps using indices from a bridged string after converting the
|
||||
// string to a native representation. Such indices are invalid, but
|
||||
// returning nil here can break code that appeared to work fine for ASCII
|
||||
// strings in Swift releases prior to 5.7.
|
||||
guard
|
||||
let i = target._guts.ensureMatchingEncodingNoTrap(sourcePosition),
|
||||
target._isValidIndex(i)
|
||||
else { return nil }
|
||||
// when `self` is a UTF-8 string (or vice versa), to preserve compatibility
|
||||
// with (broken) code that keeps using indices from a bridged string after
|
||||
// converting the string to a native representation. Such indices are
|
||||
// invalid, but returning nil here can break code that appeared to work fine
|
||||
// for ASCII strings in Swift releases prior to 5.7.
|
||||
let i = target._guts.ensureMatchingEncoding(sourcePosition)
|
||||
guard target._isValidIndex(i) else { return nil }
|
||||
self = i._characterAligned
|
||||
}
|
||||
|
||||
@@ -111,15 +109,13 @@ extension String.Index {
|
||||
}
|
||||
if let str = target as? Substring {
|
||||
// As a special exception, we allow `sourcePosition` to be an UTF-16 index
|
||||
// when `self` is a UTF-8 string, to preserve compatibility with (broken)
|
||||
// code that keeps using indices from a bridged string after converting
|
||||
// the string to a native representation. Such indices are invalid, but
|
||||
// returning nil here can break code that appeared to work fine for ASCII
|
||||
// strings in Swift releases prior to 5.7.
|
||||
guard
|
||||
let i = str._wholeGuts.ensureMatchingEncodingNoTrap(sourcePosition),
|
||||
str._isValidIndex(i)
|
||||
else { return nil }
|
||||
// when `self` is a UTF-8 string (or vice versa), to preserve
|
||||
// compatibility with (broken) code that keeps using indices from a
|
||||
// bridged string after converting the string to a native representation.
|
||||
// Such indices are invalid, but returning nil here can break code that
|
||||
// appeared to work fine for ASCII strings in Swift releases prior to 5.7.
|
||||
let i = str._wholeGuts.ensureMatchingEncoding(sourcePosition)
|
||||
guard str._isValidIndex(i) else { return nil }
|
||||
self = i
|
||||
return
|
||||
}
|
||||
|
||||
@@ -342,3 +342,100 @@ extension _StringGuts {
|
||||
return Range(_uncheckedBounds: (l, u))
|
||||
}
|
||||
}
|
||||
|
||||
// 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<String.Index>
|
||||
) -> Range<String.Index> {
|
||||
let upper = ensureMatchingEncoding(range.upperBound)
|
||||
let lower = ensureMatchingEncoding(range.lowerBound)
|
||||
|
||||
_precondition(upper <= endIndex && lower <= upper,
|
||||
"String index range is out of bounds")
|
||||
|
||||
return 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<String.Index>
|
||||
) -> Range<String.Index> {
|
||||
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 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)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,9 +239,16 @@ extension String.UTF16View: BidirectionalCollection {
|
||||
let start = _guts.ensureMatchingEncoding(start)
|
||||
let end = _guts.ensureMatchingEncoding(end)
|
||||
|
||||
_precondition(start._encodedOffset <= _guts.count,
|
||||
// FIXME: This method used to not properly validate indices before 5.7;
|
||||
// temporarily allow older binaries to keep invoking undefined behavior as
|
||||
// before.
|
||||
_precondition(
|
||||
ifLinkedOnOrAfter: .v5_7_0,
|
||||
start._encodedOffset <= _guts.count,
|
||||
"String index is out of bounds")
|
||||
_precondition(end._encodedOffset <= _guts.count,
|
||||
_precondition(
|
||||
ifLinkedOnOrAfter: .v5_7_0,
|
||||
end._encodedOffset <= _guts.count,
|
||||
"String index is out of bounds")
|
||||
|
||||
if _slowPath(_guts.isForeign) {
|
||||
@@ -403,15 +410,13 @@ extension String.UTF16View.Index {
|
||||
_ idx: String.Index, within target: String.UTF16View
|
||||
) {
|
||||
// As a special exception, we allow `idx` to be an UTF-16 index when `self`
|
||||
// is a UTF-8 string, to preserve compatibility with (broken) code that
|
||||
// keeps using indices from a bridged string after converting the string to
|
||||
// a native representation. Such indices are invalid, but returning nil here
|
||||
// can break code that appeared to work fine for ASCII strings in Swift
|
||||
// releases prior to 5.7.
|
||||
guard
|
||||
let idx = target._guts.ensureMatchingEncodingNoTrap(idx),
|
||||
idx._encodedOffset <= target._guts.count
|
||||
else { return nil }
|
||||
// is a UTF-8 string (or vice versa), to preserve compatibility with
|
||||
// (broken) code that keeps using indices from a bridged string after
|
||||
// converting the string to a native representation. Such indices are
|
||||
// invalid, but returning nil here can break code that appeared to work fine
|
||||
// for ASCII strings in Swift releases prior to 5.7.
|
||||
let idx = target._guts.ensureMatchingEncoding(idx)
|
||||
guard idx._encodedOffset <= target._guts.count else { return nil }
|
||||
|
||||
if _slowPath(target._guts.isForeign) {
|
||||
guard idx._foreignIsWithin(target) else { return nil }
|
||||
|
||||
@@ -360,15 +360,13 @@ extension String.UTF8View.Index {
|
||||
// Note: This method used to be inlinable until Swift 5.7.
|
||||
|
||||
// As a special exception, we allow `idx` to be an UTF-16 index when `self`
|
||||
// is a UTF-8 string, to preserve compatibility with (broken) code that
|
||||
// keeps using indices from a bridged string after converting the string to
|
||||
// a native representation. Such indices are invalid, but returning nil here
|
||||
// can break code that appeared to work fine for ASCII strings in Swift
|
||||
// releases prior to 5.7.
|
||||
guard
|
||||
let idx = target._guts.ensureMatchingEncodingNoTrap(idx),
|
||||
idx._encodedOffset <= target._guts.count
|
||||
else { return nil }
|
||||
// is a UTF-8 string (or vice versa), to preserve compatibility with
|
||||
// (broken) code that keeps using indices from a bridged string after
|
||||
// converting the string to a native representation. Such indices are
|
||||
// invalid, but returning nil here can break code that appeared to work fine
|
||||
// for ASCII strings in Swift releases prior to 5.7.
|
||||
let idx = target._guts.ensureMatchingEncoding(idx)
|
||||
guard idx._encodedOffset <= target._guts.count else { return nil }
|
||||
|
||||
if _slowPath(target._guts.isForeign) {
|
||||
guard idx._foreignIsWithin(target) else { return nil }
|
||||
|
||||
@@ -390,7 +390,10 @@ extension String.UnicodeScalarView: RangeReplaceableCollection {
|
||||
_ subrange: Range<Index>,
|
||||
with newElements: C
|
||||
) where C: Collection, C.Element == Unicode.Scalar {
|
||||
let subrange = _guts.validateScalarRange(subrange)
|
||||
// FIXME: This method used to not properly validate indices before 5.7;
|
||||
// temporarily allow older binaries to keep invoking undefined behavior as
|
||||
// before.
|
||||
let subrange = _guts.validateScalarRange_5_7(subrange)
|
||||
_guts.replaceSubrange(subrange, with: newElements)
|
||||
_invariantCheck()
|
||||
}
|
||||
@@ -428,8 +431,14 @@ extension String.UnicodeScalarIndex {
|
||||
_ sourcePosition: String.Index,
|
||||
within unicodeScalars: String.UnicodeScalarView
|
||||
) {
|
||||
// As a special exception, we allow `sourcePosition` to be an UTF-16 index
|
||||
// when `self` is a UTF-8 string (or vice versa), to preserve compatibility
|
||||
// with (broken) code that keeps using indices from a bridged string after
|
||||
// converting the string to a native representation. Such indices are
|
||||
// invalid, but returning nil here can break code that appeared to work fine
|
||||
// for ASCII strings in Swift releases prior to 5.7.
|
||||
let i = unicodeScalars._guts.ensureMatchingEncoding(sourcePosition)
|
||||
guard
|
||||
let i = unicodeScalars._guts.ensureMatchingEncodingNoTrap(sourcePosition),
|
||||
i._encodedOffset <= unicodeScalars._guts.count,
|
||||
unicodeScalars._guts.isOnUnicodeScalarBoundary(i)
|
||||
else {
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "swift/Runtime/Config.h"
|
||||
#include "swift/Runtime/Bincompat.h"
|
||||
#include "swift/Runtime/Once.h"
|
||||
#include "swift/Runtime/EnvironmentVariables.h"
|
||||
#include "../SwiftShims/RuntimeShims.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// If this is an Apple OS, use the Apple binary compatibility rules
|
||||
@@ -58,6 +62,57 @@ static enum sdk_test isAppAtLeastSpring2021() {
|
||||
}
|
||||
#endif
|
||||
|
||||
static _SwiftStdlibVersion binCompatVersionOverride = { 0 };
|
||||
|
||||
static _SwiftStdlibVersion const knownVersions[] = {
|
||||
{ /* 5.6.0 */0x050600 },
|
||||
{ /* 5.7.0 */0x050700 },
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
static bool isKnownBinCompatVersion(_SwiftStdlibVersion version) {
|
||||
for (int i = 0; knownVersions[i]._value != 0; ++i) {
|
||||
if (knownVersions[i]._value == version._value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void checkBinCompatEnvironmentVariable(void *context) {
|
||||
_SwiftStdlibVersion version =
|
||||
{ runtime::environment::SWIFT_BINARY_COMPATIBILITY_VERSION() };
|
||||
|
||||
if (version._value > 0 && !isKnownBinCompatVersion(version)) {
|
||||
swift::warning(RuntimeErrorFlagNone,
|
||||
"Warning: ignoring unknown SWIFT_BINARY_COMPATIBILITY_VERSION %x.\n",
|
||||
version._value);
|
||||
return;
|
||||
}
|
||||
|
||||
binCompatVersionOverride = version;
|
||||
}
|
||||
|
||||
extern "C" __swift_bool _swift_stdlib_isExecutableLinkedOnOrAfter(
|
||||
_SwiftStdlibVersion version
|
||||
) {
|
||||
static OnceToken_t getenvToken;
|
||||
SWIFT_ONCE_F(getenvToken, checkBinCompatEnvironmentVariable, nullptr);
|
||||
|
||||
if (binCompatVersionOverride._value > 0) {
|
||||
return version._value <= binCompatVersionOverride._value;
|
||||
}
|
||||
|
||||
#if BINARY_COMPATIBILITY_APPLE
|
||||
// Return true for all known versions for now -- we can't map them to OS
|
||||
// versions at this time.
|
||||
return isKnownBinCompatVersion(version);
|
||||
|
||||
#else // !BINARY_COMPATIBILITY_APPLE
|
||||
return isKnownBinCompatVersion(version);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Should we mimic the old override behavior when scanning protocol conformance records?
|
||||
|
||||
// Old apps expect protocol conformances to override each other in a particular
|
||||
|
||||
@@ -93,6 +93,36 @@ static uint8_t parse_uint8_t(const char *name,
|
||||
return n;
|
||||
}
|
||||
|
||||
static uint32_t parse_uint32_t(const char *name,
|
||||
const char *value,
|
||||
uint32_t defaultValue) {
|
||||
if (!value)
|
||||
return defaultValue;
|
||||
char *end;
|
||||
long long n = strtoll(value, &end, 0);
|
||||
if (*end != '\0') {
|
||||
swift::warning(RuntimeErrorFlagNone,
|
||||
"Warning: cannot parse value %s=%s, defaulting to %u.\n",
|
||||
name, value, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (n < 0) {
|
||||
swift::warning(RuntimeErrorFlagNone,
|
||||
"Warning: %s=%s out of bounds, clamping to 0.\n",
|
||||
name, value);
|
||||
return 0;
|
||||
}
|
||||
if (n > UINT32_MAX) {
|
||||
swift::warning(RuntimeErrorFlagNone,
|
||||
"Warning: %s=%s out of bounds, clamping to %d.\n",
|
||||
name, value, UINT32_MAX);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// Print a list of all the environment variables. Lazy initialization makes
|
||||
// this a bit odd, but the use of these variables in the metadata system means
|
||||
// it's almost certain to run early.
|
||||
|
||||
@@ -69,4 +69,7 @@ VARIABLE(SWIFT_DEBUG_RUNTIME_EXCLUSIVITY_LOGGING, bool, false,
|
||||
|
||||
#endif
|
||||
|
||||
VARIABLE(SWIFT_BINARY_COMPATIBILITY_VERSION, uint32_t, 0,
|
||||
"Set the binary compatibility level of the Swift Standard Library")
|
||||
|
||||
#undef VARIABLE
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// RUN: %target-run-stdlib-swift %S/Inputs/
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift -g %s -o %t/StringIndex
|
||||
// RUN: %target-codesign %t/StringIndex
|
||||
// RUN: env %env-SWIFT_BINARY_COMPATIBILITY_VERSION=0x050700 %target-run %t/StringIndex %S/Inputs/
|
||||
|
||||
// Note: the environment variable above forces the stdlib's bincompat version to
|
||||
// 5.7 so that we can test new behavior even if the SDK we're using predates it.
|
||||
|
||||
// REQUIRES: executable_test
|
||||
// UNSUPPORTED: freestanding
|
||||
|
||||
@@ -755,7 +762,7 @@ suite.test("Global vs local grapheme cluster boundaries") {
|
||||
}
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/subscripts") {
|
||||
suite.test("Index encoding correction/UTF-16→8/subscripts") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
@@ -778,10 +785,10 @@ suite.test("Index encoding correction/subscripts") {
|
||||
// automatically converts UTF-16-encoded indices to UTF-8 when needed.
|
||||
// This can be quite slow, but it always produces the "right" results.
|
||||
//
|
||||
// This conversion is one way only (see StringTraps.swift for the other
|
||||
// direction), and it does not account for the effect of the actual mutation.
|
||||
// If the mutation's effect included the data addressed by the original index,
|
||||
// then we may still get nonsensical results.
|
||||
// This conversion also works in the other direction, and it does not account
|
||||
// for the effect of the actual mutation. If the mutation's effect included
|
||||
// the data addressed by the original index, then we may still get nonsensical
|
||||
// results.
|
||||
var s = ("🫱🏼🫲🏽 a 🧑🏽🌾 b" as NSString) as String
|
||||
//s.dumpIndices()
|
||||
|
||||
@@ -802,7 +809,36 @@ suite.test("Index encoding correction/subscripts") {
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/conversions/characters") {
|
||||
suite.test("Index encoding correction/UTF-8→16/subscripts") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
}
|
||||
// This tests the UTF-8 to UTF-16 direction of the special case in String's
|
||||
// index validation above.
|
||||
let native = "🫱🏼🫲🏽 a 🧑🏽🌾 b"
|
||||
let cocoa = ("🫱🏼🫲🏽 a 🧑🏽🌾 b" as NSString) as String
|
||||
|
||||
let nativeIndices = native.allIndices(includingEnd: false).map {
|
||||
(
|
||||
$0,
|
||||
native[$0],
|
||||
native.unicodeScalars[$0],
|
||||
native.utf8[$0],
|
||||
native.utf16[$0])
|
||||
}
|
||||
|
||||
for (i, char, scalar, u8, u16) in nativeIndices {
|
||||
expectEqual(cocoa[i], char, "i: \(i)")
|
||||
expectEqual(cocoa.unicodeScalars[i], scalar, "i: \(i)")
|
||||
expectEqual(cocoa.utf8[i], u8, "i: \(i)")
|
||||
expectEqual(cocoa.utf16[i], u16, "i: \(i)")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/UTF-16→8/conversions/characters") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
@@ -822,7 +858,26 @@ suite.test("Index encoding correction/conversions/characters") {
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/conversions/scalars") {
|
||||
suite.test("Index encoding correction/UTF-8→16/conversions/characters") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
}
|
||||
let native = "🫱🏼🫲🏽 a 🧑🏽🌾 b"
|
||||
let cocoa = ("🫱🏼🫲🏽 a 🧑🏽🌾 b" as NSString) as String
|
||||
|
||||
let chars = native.indices.map { ($0, native[$0]) }
|
||||
|
||||
for (i, char) in chars {
|
||||
let j = String.Index(i, within: cocoa)
|
||||
expectNotNil(j, "i: \(i)")
|
||||
expectEqual(j.map { cocoa[$0] }, char, "i: \(i)")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/UTF-16→8/conversions/scalars") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
@@ -842,7 +897,28 @@ suite.test("Index encoding correction/conversions/scalars") {
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/conversions/UTF-8") {
|
||||
suite.test("Index encoding correction/UTF-8→16/conversions/scalars") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
}
|
||||
let native = "🫱🏼🫲🏽 a 🧑🏽🌾 b"
|
||||
let cocoa = ("🫱🏼🫲🏽 a 🧑🏽🌾 b" as NSString) as String
|
||||
|
||||
let scalars = native.unicodeScalars.indices.map {
|
||||
($0, native.unicodeScalars[$0])
|
||||
}
|
||||
|
||||
for (i, scalar) in scalars {
|
||||
let j = String.Index(i, within: cocoa.unicodeScalars)
|
||||
expectNotNil(j, "i: \(i)")
|
||||
expectEqual(j.map { cocoa.unicodeScalars[$0] }, scalar, "i: \(i)")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/UTF-16→8/conversions/UTF-8") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
@@ -862,7 +938,26 @@ suite.test("Index encoding correction/conversions/UTF-8") {
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/conversions/UTF-16") {
|
||||
suite.test("Index encoding correction/UTF-8→16/conversions/UTF-8") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
}
|
||||
let native = "🫱🏼🫲🏽 a 🧑🏽🌾 b"
|
||||
let cocoa = ("🫱🏼🫲🏽 a 🧑🏽🌾 b" as NSString) as String
|
||||
|
||||
let utf8Units = native.utf8.indices.map { ($0, native.utf8[$0]) }
|
||||
|
||||
for (i, u8) in utf8Units {
|
||||
let j = String.Index(i, within: cocoa.utf8)
|
||||
expectNotNil(j, "i: \(i)")
|
||||
expectEqual(j.map { cocoa.utf8[$0] }, u8, "i: \(i)")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/UTF-16→8/conversions/UTF-16") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
@@ -881,6 +976,25 @@ suite.test("Index encoding correction/conversions/UTF-16") {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
suite.test("Index encoding correction/UTF-8→16/conversions/UTF-16") {
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
// String indices did not track their encoding until 5.7.
|
||||
return
|
||||
}
|
||||
let native = "🫱🏼🫲🏽 a 🧑🏽🌾 b"
|
||||
let cocoa = ("🫱🏼🫲🏽 a 🧑🏽🌾 b" as NSString) as String
|
||||
|
||||
let utf16Units = native.utf16.indices.map { ($0, native.utf16[$0]) }
|
||||
|
||||
for (i, u16) in utf16Units {
|
||||
let j = String.Index(i, within: cocoa.utf16)
|
||||
expectNotNil(j, "i: \(i)")
|
||||
expectEqual(j.map { cocoa.utf16[$0] }, u16, "i: \(i)")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
suite.test("String.replaceSubrange index validation")
|
||||
.forEach(in: examples) { string in
|
||||
guard #available(SwiftStdlib 5.7, *) else {
|
||||
|
||||
29
test/stdlib/StringIndex_bincompat.swift
Normal file
29
test/stdlib/StringIndex_bincompat.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift -g %s -o %t/StringIndex
|
||||
// RUN: %target-codesign %t/StringIndex
|
||||
// RUN: env %env-SWIFT_BINARY_COMPATIBILITY_VERSION=0x050600 %target-run %t/StringIndex %S/Inputs/
|
||||
|
||||
// REQUIRES: executable_test
|
||||
// UNSUPPORTED: freestanding
|
||||
// UNSUPPORTED: use_os_stdlib || back_deployment_runtime
|
||||
// UNSUPPORTED: swift_stdlib_asserts
|
||||
|
||||
// In 5.7, a number of String APIs started performing stronger index validation,
|
||||
// eliminating issues such as out of bounds memory accesses when members are
|
||||
// given invalid indices. The environment variable
|
||||
// SWIFT_BINARY_COMPATIBILITY_VERSION above forces the stdlib's bincompat
|
||||
// version to 5.6 so that we can test older behavior.
|
||||
//
|
||||
// We can only test old behavior that doesn't lead to undefined behavior,
|
||||
// though.
|
||||
|
||||
import StdlibUnittest
|
||||
|
||||
var suite = TestSuite("StringIndexBinCompatTests")
|
||||
defer { runAllTests() }
|
||||
|
||||
suite.test("String.index(before:) on an index near the start") {
|
||||
let string = "😲 hi"
|
||||
let i = string.utf8.index(after: string.utf8.startIndex)
|
||||
expectEqual(string.index(before: i), string.startIndex)
|
||||
}
|
||||
@@ -4,8 +4,12 @@
|
||||
//
|
||||
// RUN: %target-codesign %t/a.out_Debug
|
||||
// RUN: %target-codesign %t/a.out_Release
|
||||
// RUN: %target-run %t/a.out_Debug
|
||||
// RUN: %target-run %t/a.out_Release
|
||||
// RUN: env %env-SWIFT_BINARY_COMPATIBILITY_VERSION=0x050700 %target-run %t/a.out_Debug
|
||||
// RUN: env %env-SWIFT_BINARY_COMPATIBILITY_VERSION=0x050700 %target-run %t/a.out_Release
|
||||
|
||||
// Note: the environment variable above forces the stdlib's bincompat version to
|
||||
// 5.7 so that we can test new behavior even if the SDK we're using predates it.
|
||||
|
||||
// REQUIRES: executable_test
|
||||
// UNSUPPORTED: OS=wasi
|
||||
|
||||
@@ -353,114 +357,3 @@ StringTraps.test("UTF8View foreign index(before:) trap on i == startIndex")
|
||||
i = s.utf8.index(before: i)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
if #available(SwiftStdlib 5.7, *) {
|
||||
let native = "🫱🏼🫲🏽 a 🧑🏽🌾 b"
|
||||
let cocoa = ("🫱🏼🫲🏽 a 🧑🏽🌾 b" as NSString) as String
|
||||
|
||||
let goodIndices: [String.Index] = [
|
||||
native.startIndex,
|
||||
native.unicodeScalars.startIndex,
|
||||
native.utf8.startIndex,
|
||||
native.utf16.startIndex,
|
||||
]
|
||||
|
||||
StringTraps.test("Start index encoding").forEach(in: goodIndices) { i in
|
||||
// The startIndex works fine in both encodings.
|
||||
print(i)
|
||||
expectEqual(cocoa[i], native[i])
|
||||
}
|
||||
|
||||
let badIndices: [String.Index] = [
|
||||
native.index(native.startIndex, offsetBy: 3),
|
||||
native.unicodeScalars.index(native.startIndex, offsetBy: 3),
|
||||
native.utf8.index(native.startIndex, offsetBy: 3),
|
||||
native.utf16.index(native.startIndex, offsetBy: 3),
|
||||
]
|
||||
|
||||
StringTraps.test("String.subscript/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa[i])
|
||||
}
|
||||
|
||||
StringTraps.test("String.index(after:)/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.index(after: i))
|
||||
}
|
||||
|
||||
StringTraps.test("String.index(before:)/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.index(before: i))
|
||||
}
|
||||
|
||||
StringTraps.test("String.UnicodeScalarView.subscript/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.unicodeScalars[i])
|
||||
}
|
||||
|
||||
StringTraps.test("String.UnicodeScalarView.index(after:)/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.unicodeScalars.index(after: i))
|
||||
}
|
||||
|
||||
StringTraps.test("String.UnicodeScalarView.index(before:)/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.unicodeScalars.index(before: i))
|
||||
}
|
||||
|
||||
StringTraps.test("String.UTF8View.subscript/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.utf8[i])
|
||||
}
|
||||
|
||||
StringTraps.test("String.UTF8View.index(after:)/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.utf8.index(after: i))
|
||||
}
|
||||
|
||||
StringTraps.test("String.UTF8View.index(before:)/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.utf8.index(before: i))
|
||||
}
|
||||
|
||||
StringTraps.test("String.UTF16View.subscript/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.utf16[i])
|
||||
}
|
||||
|
||||
StringTraps.test("String.UTF16View.index(after:)/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.utf16.index(after: i))
|
||||
}
|
||||
|
||||
StringTraps.test("String.UTF16View.index(before:)/encoding trap")
|
||||
.forEach(in: badIndices) { i in
|
||||
print(i)
|
||||
expectCrashLater()
|
||||
print(cocoa.utf16.index(before: i))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user