Files
swift-mirror/stdlib/public/core/CocoaArray.swift
Gabor Horvath 402ad33463 [StrictMemorySafety] Check the safety of return types of calls
Previously, we skipped checking the return type of a function for safety
as we expected to warn at the use of the returned value:

  let x = returnsUnsafe()
  usesUnsafe(x) // warn here

Unfortunately, this resulted in missing some unsafe constructs that can
introduce memory safety issues when the use of the return value had a
different shape resulting in false negatives for cases like:

  return returnsUnsafe()

or

  usesUnsafe(returnsUnsafe())

This PR changes the analysis to always take return types of function
calls into account.

rdar://157237301
2025-08-05 12:16:44 +01:00

172 lines
5.6 KiB
Swift

//===--- CocoaArray.swift - A subset of the NSArray interface -------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
//
// To implement bridging, the core standard library needs to interact
// a little bit with Cocoa. Because we want to keep the core
// decoupled from the Foundation module, we can't use NSArray
// directly. We _can_, however, use an @objc protocol with a
// compatible API. That's _NSArrayCore.
//
//===----------------------------------------------------------------------===//
#if _runtime(_ObjC)
import SwiftShims
/// A wrapper around any `_NSArrayCore` (represented as AnyObject) that gives it
/// `Collection` conformance. Why not make `_NSArrayCore` conform directly?
/// It's a class, and I don't want to pay for the dynamic dispatch overhead.
@usableFromInline
@frozen
internal struct _CocoaArrayWrapper: RandomAccessCollection {
@usableFromInline
typealias Indices = Range<Int>
@usableFromInline
internal var buffer: AnyObject
@usableFromInline @_transparent
internal init(_ buffer: AnyObject) {
self.buffer = buffer
}
internal var core: _NSArrayCore {
@inline(__always) get {
return unsafe unsafeBitCast(buffer, to: _NSArrayCore.self)
}
}
@inlinable
internal var startIndex: Int {
return 0
}
@usableFromInline
internal var endIndex: Int {
@_effects(releasenone) get {
core.count
}
}
@usableFromInline
internal subscript(i: Int) -> AnyObject {
@_effects(releasenone) get {
core.objectAt(i)
}
}
@usableFromInline
internal subscript(bounds: Range<Int>) -> _SliceBuffer<AnyObject> {
let boundsCount = bounds.count
if boundsCount == 0 {
return _SliceBuffer(
_buffer: _ContiguousArrayBuffer<AnyObject>(),
shiftedToStartIndex: bounds.lowerBound)
}
// Look for contiguous storage in the NSArray
let cocoaStorageBaseAddress = unsafe self.contiguousStorage(self.indices)
if let cocoaStorageBaseAddress = unsafe cocoaStorageBaseAddress {
return unsafe _SliceBuffer(
owner: self.buffer,
subscriptBaseAddress: cocoaStorageBaseAddress,
indices: bounds,
hasNativeBuffer: false)
}
// No contiguous storage found; we must allocate
let result = _ContiguousArrayBuffer<AnyObject>(
_uninitializedCount: boundsCount,
minimumCapacity: 0)
let base = unsafe UnsafeMutableRawPointer(result.firstElementAddress)
.assumingMemoryBound(to: AnyObject.self)
for idx in 0..<boundsCount {
unsafe (base + idx).initialize(to: core.objectAt(idx + bounds.lowerBound))
}
return _SliceBuffer(_buffer: result, shiftedToStartIndex: bounds.lowerBound)
}
/// Returns a pointer to the first element in the given non-empty `subRange`
/// if the subRange is stored contiguously. Otherwise, return `nil`.
///
/// The "non-empty" condition saves a branch within this method that can
/// likely be better handled in a caller.
///
/// - Note: This method should only be used as an optimization; it
/// is sometimes conservative and may return `nil` even when
/// contiguous storage exists, e.g., if array doesn't have a smart
/// implementation of countByEnumerating.
internal func contiguousStorage(
_ subRange: Range<Int>
) -> UnsafeMutablePointer<AnyObject>?
{
_internalInvariant(!subRange.isEmpty)
var enumerationState = unsafe _makeSwiftNSFastEnumerationState()
// This function currently returns nil unless the first
// subRange.upperBound items are stored contiguously. This is an
// acceptable conservative behavior, but could potentially be
// optimized for other cases.
let contiguousCount = unsafe withUnsafeMutablePointer(to: &enumerationState) {
unsafe core.countByEnumerating(with: $0, objects: nil, count: 0)
}
return unsafe contiguousCount >= subRange.upperBound
? UnsafeMutableRawPointer(enumerationState.itemsPtr!)
.assumingMemoryBound(to: AnyObject.self)
+ subRange.lowerBound
: nil
}
@usableFromInline
__consuming internal func _copyContents(
subRange bounds: Range<Int>,
initializing target: UnsafeMutablePointer<AnyObject>
) -> UnsafeMutablePointer<AnyObject> {
return unsafe withExtendedLifetime(buffer) {
let nsSubRange = SwiftShims._SwiftNSRange(
location: bounds.lowerBound,
length: bounds.upperBound - bounds.lowerBound)
// Copies the references out of the NSArray without retaining them
unsafe core.getObjects(target, range: nsSubRange)
// Make another pass to retain the copied objects
var result = unsafe target
for _ in bounds {
unsafe result.initialize(to: result.pointee)
unsafe result += 1
}
return unsafe result
}
}
@_alwaysEmitIntoClient
internal __consuming func _copyContents(
initializing buffer: UnsafeMutableBufferPointer<Element>
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
guard buffer.count > 0 else { return (makeIterator(), 0) }
let start = buffer.baseAddress!
let c = Swift.min(self.count, buffer.count)
_ = unsafe _copyContents(subRange: 0 ..< c, initializing: start)
return (IndexingIterator(_elements: self, _position: c), c)
}
}
@available(*, unavailable)
extension _CocoaArrayWrapper: Sendable {}
#endif