Skip to content

[stdlib] Unchecked bounds are unsafe #79813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions stdlib/public/core/ClosedRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public struct ClosedRange<Bound: Comparable> {

// This works around _debugPrecondition() impacting the performance of
// optimized code. (rdar://72246338)
@unsafe
@_alwaysEmitIntoClient @inline(__always)
internal init(_uncheckedBounds bounds: (lower: Bound, upper: Bound)) {
self.lowerBound = bounds.lower
Expand All @@ -89,7 +90,9 @@ public struct ClosedRange<Bound: Comparable> {
public init(uncheckedBounds bounds: (lower: Bound, upper: Bound)) {
_debugPrecondition(bounds.lower <= bounds.upper,
"ClosedRange requires lowerBound <= upperBound")
self.init(_uncheckedBounds: (lower: bounds.lower, upper: bounds.upper))
unsafe self.init(
_uncheckedBounds: (lower: bounds.lower, upper: bounds.upper)
)
}
}

Expand All @@ -109,7 +112,7 @@ extension ClosedRange: RangeExpression {
@inlinable // trivial-implementation
public func relative<C: Collection>(to collection: C) -> Range<Bound>
where C.Index == Bound {
return Range(
return unsafe Range(
_uncheckedBounds: (
lower: lowerBound,
upper: collection.index(after: self.upperBound)))
Expand Down Expand Up @@ -405,7 +408,7 @@ extension Comparable {
public static func ... (minimum: Self, maximum: Self) -> ClosedRange<Self> {
_precondition(
minimum <= maximum, "Range requires lowerBound <= upperBound")
return ClosedRange(_uncheckedBounds: (lower: minimum, upper: maximum))
return unsafe ClosedRange(_uncheckedBounds: (lower: minimum, upper: maximum))
}
}

Expand Down Expand Up @@ -496,7 +499,7 @@ extension ClosedRange {
limits.upperBound < self.upperBound ? limits.upperBound
: limits.lowerBound > self.upperBound ? limits.lowerBound
: self.upperBound
return ClosedRange(_uncheckedBounds: (lower: lower, upper: upper))
return unsafe ClosedRange(_uncheckedBounds: (lower: lower, upper: upper))
}
}

Expand All @@ -512,7 +515,9 @@ extension ClosedRange where Bound: Strideable, Bound.Stride: SignedInteger {
public init(_ other: Range<Bound>) {
_precondition(!other.isEmpty, "Can't form an empty closed range")
let upperBound = other.upperBound.advanced(by: -1)
self.init(_uncheckedBounds: (lower: other.lowerBound, upper: upperBound))
unsafe self.init(
_uncheckedBounds: (lower: other.lowerBound, upper: upperBound)
)
}
}

Expand Down Expand Up @@ -581,7 +586,7 @@ extension ClosedRange: Decodable where Bound: Decodable {
codingPath: decoder.codingPath,
debugDescription: "Cannot initialize \(ClosedRange.self) with a lowerBound (\(lowerBound)) greater than upperBound (\(upperBound))"))
}
self.init(_uncheckedBounds: (lower: lowerBound, upper: upperBound))
unsafe self.init(_uncheckedBounds: (lower: lowerBound, upper: upperBound))
}
}

Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/InlineArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ extension InlineArray where Element: ~Copyable {
@_alwaysEmitIntoClient
@_transparent
public var indices: Range<Int> {
Range(_uncheckedBounds: (0, count))
unsafe Range(_uncheckedBounds: (0, count))
}

/// Returns the position immediately after the given index.
Expand Down
17 changes: 11 additions & 6 deletions stdlib/public/core/Range.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public struct Range<Bound: Comparable> {

// This works around _debugPrecondition() impacting the performance of
// optimized code. (rdar://72246338)
@unsafe
@_alwaysEmitIntoClient @inline(__always)
internal init(_uncheckedBounds bounds: (lower: Bound, upper: Bound)) {
self.lowerBound = bounds.lower
Expand All @@ -178,7 +179,9 @@ public struct Range<Bound: Comparable> {
public init(uncheckedBounds bounds: (lower: Bound, upper: Bound)) {
_debugPrecondition(bounds.lower <= bounds.upper,
"Range requires lowerBound <= upperBound")
self.init(_uncheckedBounds: (lower: bounds.lower, upper: bounds.upper))
unsafe self.init(
_uncheckedBounds: (lower: bounds.lower, upper: bounds.upper)
)
}

/// Returns a Boolean value indicating whether the given element is contained
Expand Down Expand Up @@ -321,7 +324,9 @@ extension Range where Bound: Strideable, Bound.Stride: SignedInteger {
@inlinable // trivial-implementation
public init(_ other: ClosedRange<Bound>) {
let upperBound = other.upperBound.advanced(by: 1)
self.init(_uncheckedBounds: (lower: other.lowerBound, upper: upperBound))
unsafe self.init(
_uncheckedBounds: (lower: other.lowerBound, upper: upperBound)
)
}
}

Expand Down Expand Up @@ -372,7 +377,7 @@ extension Range {
limits.upperBound < self.upperBound ? limits.upperBound
: limits.lowerBound > self.upperBound ? limits.lowerBound
: self.upperBound
return Range(_uncheckedBounds: (lower: lower, upper: upper))
return unsafe Range(_uncheckedBounds: (lower: lower, upper: upper))
}
}

Expand Down Expand Up @@ -453,7 +458,7 @@ extension Range: Decodable where Bound: Decodable {
codingPath: decoder.codingPath,
debugDescription: "Cannot initialize \(Range.self) with a lowerBound (\(lowerBound)) greater than upperBound (\(upperBound))"))
}
self.init(_uncheckedBounds: (lower: lowerBound, upper: upperBound))
unsafe self.init(_uncheckedBounds: (lower: lowerBound, upper: upperBound))
}
}

Expand Down Expand Up @@ -754,7 +759,7 @@ extension Comparable {
public static func ..< (minimum: Self, maximum: Self) -> Range<Self> {
_precondition(minimum <= maximum,
"Range requires lowerBound <= upperBound")
return Range(_uncheckedBounds: (lower: minimum, upper: maximum))
return unsafe Range(_uncheckedBounds: (lower: minimum, upper: maximum))
}

/// Returns a partial range up to, but not including, its upper bound.
Expand Down Expand Up @@ -1123,7 +1128,7 @@ extension Range where Bound == String.Index {
_internalInvariant(
(lowerBound._canBeUTF8 && upperBound._canBeUTF8)
|| (lowerBound._canBeUTF16 && upperBound._canBeUTF16))
return Range<Int>(
return unsafe Range<Int>(
_uncheckedBounds: (lowerBound._encodedOffset, upperBound._encodedOffset))
}
}
2 changes: 1 addition & 1 deletion stdlib/public/core/Slice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public struct Slice<Base: Collection> {

@_alwaysEmitIntoClient @inline(__always)
internal var _bounds: Range<Base.Index> {
Range(_uncheckedBounds: (_startIndex, _endIndex))
unsafe Range(_uncheckedBounds: (_startIndex, _endIndex))
}
}

Expand Down
6 changes: 3 additions & 3 deletions stdlib/public/core/Span/RawSpan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ extension RawSpan {
/// - Complexity: O(1)
@_alwaysEmitIntoClient
public var byteOffsets: Range<Int> {
.init(_uncheckedBounds: (0, byteCount))
unsafe Range(_uncheckedBounds: (0, byteCount))
}
}

Expand Down Expand Up @@ -629,13 +629,13 @@ extension RawSpan {
public func byteOffsets(of other: borrowing Self) -> Range<Int>? {
if other._count > _count { return nil }
guard let spanStart = other._pointer, _count > 0 else {
return unsafe _pointer == other._pointer ? Range(_uncheckedBounds: (0, 0)) : nil
return unsafe _pointer == other._pointer ? 0..<0 : nil
}
let start = _start()
let spanEnd = unsafe spanStart + other._count
if unsafe spanStart < start || (start + _count) < spanEnd { return nil }
let lower = unsafe start.distance(to: spanStart)
return Range(_uncheckedBounds: (lower, lower &+ other._count))
return unsafe Range(_uncheckedBounds: (lower, lower &+ other._count))
}
}

Expand Down
6 changes: 3 additions & 3 deletions stdlib/public/core/Span/Span.swift
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ extension Span where Element: ~Copyable {
/// - Complexity: O(1)
@_alwaysEmitIntoClient
public var indices: Range<Index> {
Range(_uncheckedBounds: (0, _count))
unsafe Range(_uncheckedBounds: (0, _count))
}
}

Expand Down Expand Up @@ -679,7 +679,7 @@ extension Span where Element: ~Copyable {
public func indices(of other: borrowing Self) -> Range<Index>? {
if other._count > _count { return nil }
guard let spanStart = other._pointer, _count > 0 else {
return unsafe _pointer == other._pointer ? Range(_uncheckedBounds: (0, 0)) : nil
return unsafe _pointer == other._pointer ? 0..<0 : nil
}
let start = _start()
let stride = MemoryLayout<Element>.stride
Expand All @@ -688,7 +688,7 @@ extension Span where Element: ~Copyable {
let byteOffset = unsafe start.distance(to: spanStart)
let (lower, r) = byteOffset.quotientAndRemainder(dividingBy: stride)
guard r == 0 else { return nil }
return Range(_uncheckedBounds: (lower, lower &+ other._count))
return unsafe Range(_uncheckedBounds: (lower, lower &+ other._count))
}
}

Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/StringGraphemeBreaking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ extension Unicode {
while i < buffer.endIndex {
let (next, n) = unsafe _decodeScalar(buffer, startingAt: i)
if hasBreak(before: next) {
return Range(_uncheckedBounds: (i, i &+ n))
return unsafe Range(_uncheckedBounds: (i, i &+ n))
}
i &+= n
}
Expand Down
12 changes: 6 additions & 6 deletions stdlib/public/core/StringGutsRangeReplaceable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ extension _StringGuts {
let j = result._guts.count
result.append(contentsOf: selfStr[bounds.upperBound...])
self = result._guts
return Range(_uncheckedBounds: (i, j))
return unsafe Range(_uncheckedBounds: (i, j))
}

// - Returns: The encoded offset range of the replaced contents in the result.
Expand Down Expand Up @@ -421,7 +421,7 @@ extension _StringGuts {
let j = result._guts.count
result.append(contentsOf: selfStr[bounds.upperBound...])
self = result._guts
return Range(_uncheckedBounds: (i, j))
return unsafe Range(_uncheckedBounds: (i, j))
}

// - Returns: The encoded offset range of the replaced contents in the result.
Expand All @@ -443,7 +443,7 @@ extension _StringGuts {
updateNativeStorage {
unsafe $0.replace(from: start, to: end, with: codeUnits)
}
return Range(_uncheckedBounds: (start, start + codeUnits.count))
return unsafe Range(_uncheckedBounds: (start, start + codeUnits.count))
}

// - Returns: The encoded offset range of the replaced contents in the result.
Expand All @@ -468,7 +468,7 @@ extension _StringGuts {
$0.replace(
from: start, to: end, with: codeUnits, replacementCount: replCount)
}
return Range(_uncheckedBounds: (start, start + replCount))
return unsafe Range(_uncheckedBounds: (start, start + replCount))
}

/// Run `body` to mutate the given `subrange` of this string within
Expand Down Expand Up @@ -540,9 +540,9 @@ extension _StringGuts {
let oldRange = subrange._encodedOffsetRange
let newRange = body(&self)

let oldBounds = Range(
let oldBounds = unsafe Range(
_uncheckedBounds: (startIndex._encodedOffset, endIndex._encodedOffset))
let newBounds = Range(_uncheckedBounds: (
let newBounds = unsafe Range(_uncheckedBounds: (
oldBounds.lowerBound,
oldBounds.upperBound &+ newRange.count &- oldRange.count))

Expand Down
4 changes: 2 additions & 2 deletions stdlib/public/core/StringGutsSlice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal struct _StringGutsSlice {
@inline(__always)
internal init(_ guts: _StringGuts) {
self._guts = guts
self._offsetRange = Range(_uncheckedBounds: (0, guts.count))
self._offsetRange = unsafe Range(_uncheckedBounds: (0, guts.count))
}

@inline(__always)
Expand Down Expand Up @@ -74,7 +74,7 @@ internal struct _StringGutsSlice {
._scalarAligned
let higher = String.Index(_encodedOffset: _offsetRange.upperBound)
._scalarAligned
return Range(_uncheckedBounds: (lower, higher))
return unsafe Range(_uncheckedBounds: (lower, higher))
}
}

Expand Down
12 changes: 6 additions & 6 deletions stdlib/public/core/StringIndexValidation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ extension _StringGuts {
_precondition(upper <= endIndex && lower <= upper,
"String index range is out of bounds")

return Range(_uncheckedBounds: (lower, upper))
return unsafe Range(_uncheckedBounds: (lower, upper))
}

@_alwaysEmitIntoClient
Expand All @@ -102,7 +102,7 @@ extension _StringGuts {
&& upper <= bounds.upperBound,
"Substring index range is out of bounds")

return Range(_uncheckedBounds: (lower, upper))
return unsafe Range(_uncheckedBounds: (lower, upper))
}
}

Expand Down Expand Up @@ -213,7 +213,7 @@ extension _StringGuts {
}

let r = validateSubscalarRange(range)
return Range(
return unsafe Range(
_uncheckedBounds: (scalarAlign(r.lowerBound), scalarAlign(r.upperBound)))
}

Expand Down Expand Up @@ -243,7 +243,7 @@ extension _StringGuts {
let r = validateSubscalarRange(range, in: bounds)
let upper = scalarAlign(r.upperBound)
let lower = scalarAlign(r.lowerBound)
return Range(_uncheckedBounds: (lower, upper))
return unsafe Range(_uncheckedBounds: (lower, upper))
}
}

Expand Down Expand Up @@ -356,7 +356,7 @@ extension _StringGuts {
_precondition(upper <= endIndex && lower <= upper,
"String index range is out of bounds")

return Range(_uncheckedBounds: (lower, upper))
return unsafe Range(_uncheckedBounds: (lower, upper))
}

/// A version of `validateScalarRange` that only traps if the main executable
Expand All @@ -377,7 +377,7 @@ extension _StringGuts {
}

let r = validateSubscalarRange_5_7(range)
return Range(
return unsafe Range(
_uncheckedBounds: (scalarAlign(r.lowerBound), scalarAlign(r.upperBound)))
}

Expand Down
4 changes: 3 additions & 1 deletion stdlib/public/core/StringProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ extension StringProtocol {
let end = endIndex
_internalInvariant(
start.transcodedOffset == 0 && end.transcodedOffset == 0)
return Range(_uncheckedBounds: (start._encodedOffset, end._encodedOffset))
return unsafe Range(
_uncheckedBounds: (start._encodedOffset, end._encodedOffset)
)
}
}

Expand Down
4 changes: 2 additions & 2 deletions stdlib/public/core/StringRangeReplaceableCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ extension String: RangeReplaceableCollection {
/// - Complexity: O(*n*), where *n* is the length of the string.
public mutating func insert(_ newElement: Character, at i: Index) {
let i = _guts.validateInclusiveScalarIndex(i)
let range = Range(_uncheckedBounds: (i, i))
let range = unsafe Range(_uncheckedBounds: (i, i))
_guts.replaceSubrange(range, with: newElement._str)
}

Expand All @@ -243,7 +243,7 @@ extension String: RangeReplaceableCollection {
contentsOf newElements: S, at i: Index
) where S.Element == Character {
let i = _guts.validateInclusiveScalarIndex(i)
let range = Range(_uncheckedBounds: (i, i))
let range = unsafe Range(_uncheckedBounds: (i, i))
_guts.replaceSubrange(range, with: newElements)
}

Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/StringStorageBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ extension _AbstractStringStorage {
_precondition(aRange.location + aRange.length <= Int(count),
"Range out of bounds")

let range = Range(
let range = unsafe Range(
_uncheckedBounds: (aRange.location, aRange.location+aRange.length))
let str = asString
unsafe str._copyUTF16CodeUnits(
Expand Down
Loading