Skip to content

Commit 466e26a

Browse files
committed
[stdlib] Implement _copyContents on internal Array types
`_copyContents(initializing:)` is a core method of Sequence, and it is used surprisingly often to copy stuff out of sequences. Array’s internal types currently have explicit implementations of it that trap (to prevent a performance bug due to the default iterator-based implementation. This has proved a bad idea, as not all code paths that end up calling `_copyContents` have actually been expunged — so we replaced a performance bug with a catastrophic correctness bug. 😥 Rather than trying to play whack-a-mole with code paths that end up in `_copyContents`, replace the traps with (relatively) efficient implementations, based on the ancient `_copyContents(subRange:initializing)` methods that have already been there all this time. This resolves https://bugs.swift.org/browse/SR-14663. I expect specialization will make this fix deploy back to earlier OSes in most (but unfortunately not all) cases.
1 parent b001b0b commit 466e26a

8 files changed

+224
-16
lines changed

stdlib/public/core/Array.swift

+16
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,22 @@ extension Array {
18651865
}
18661866
}
18671867

1868+
#if INTERNAL_CHECKS_ENABLED
1869+
extension Array {
1870+
// This allows us to test the `_copyContents` implementation in
1871+
// `_ArrayBuffer`. (It's like `_copyToContiguousArray` but it always makes a
1872+
// copy.)
1873+
@_alwaysEmitIntoClient
1874+
public func _copyToNewArray() -> [Element] {
1875+
Array(unsafeUninitializedCapacity: self.count) { buffer, count in
1876+
var (it, c) = self._buffer._copyContents(initializing: buffer)
1877+
_precondition(it.next() == nil)
1878+
count = c
1879+
}
1880+
}
1881+
}
1882+
#endif
1883+
18681884
#if _runtime(_ObjC)
18691885
// We isolate the bridging of the Cocoa Array -> Swift Array here so that
18701886
// in the future, we can eagerly bridge the Cocoa array. We need this function

stdlib/public/core/ArrayBuffer.swift

+13-5
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,20 @@ extension _ArrayBuffer {
311311
return UnsafeMutableRawPointer(result).assumingMemoryBound(to: Element.self)
312312
}
313313

314-
public __consuming func _copyContents(
314+
@inlinable
315+
internal __consuming func _copyContents(
315316
initializing buffer: UnsafeMutableBufferPointer<Element>
316-
) -> (Iterator,UnsafeMutableBufferPointer<Element>.Index) {
317-
// This customization point is not implemented for internal types.
318-
// Accidentally calling it would be a catastrophic performance bug.
319-
fatalError("unsupported")
317+
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
318+
if _fastPath(_isNative) {
319+
let (_, c) = _native._copyContents(initializing: buffer)
320+
return (IndexingIterator(_elements: self, _position: c), c)
321+
}
322+
guard buffer.count > 0 else { return (makeIterator(), 0) }
323+
let ptr = UnsafeMutableRawPointer(buffer.baseAddress)?
324+
.assumingMemoryBound(to: AnyObject.self)
325+
let (_, c) = _nonNative._copyContents(
326+
initializing: UnsafeMutableBufferPointer(start: ptr, count: buffer.count))
327+
return (IndexingIterator(_elements: self, _position: c), c)
320328
}
321329

322330
/// Returns a `_SliceBuffer` containing the given sub-range of elements in

stdlib/public/core/ArraySlice.swift

+16
Original file line numberDiff line numberDiff line change
@@ -1520,3 +1520,19 @@ extension ArraySlice {
15201520

15211521
extension ArraySlice: Sendable, UnsafeSendable
15221522
where Element: Sendable { }
1523+
1524+
#if INTERNAL_CHECKS_ENABLED
1525+
extension ArraySlice {
1526+
// This allows us to test the `_copyContents` implementation in
1527+
// `_SliceBuffer`. (It's like `_copyToContiguousArray` but it always makes a
1528+
// copy.)
1529+
@_alwaysEmitIntoClient
1530+
public func _copyToNewArray() -> [Element] {
1531+
Array(unsafeUninitializedCapacity: self.count) { buffer, count in
1532+
var (it, c) = self._buffer._copyContents(initializing: buffer)
1533+
_precondition(it.next() == nil)
1534+
count = c
1535+
}
1536+
}
1537+
}
1538+
#endif

stdlib/public/core/CocoaArray.swift

+11
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,16 @@ internal struct _CocoaArrayWrapper: RandomAccessCollection {
146146
}
147147
return result
148148
}
149+
150+
@_alwaysEmitIntoClient
151+
internal __consuming func _copyContents(
152+
initializing buffer: UnsafeMutableBufferPointer<Element>
153+
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
154+
guard buffer.count > 0 else { return (makeIterator(), 0) }
155+
let start = buffer.baseAddress!
156+
let c = Swift.min(self.count, buffer.count)
157+
let end = _copyContents(subRange: 0 ..< c, initializing: start)
158+
return (IndexingIterator(_elements: self, _position: c), c)
159+
}
149160
}
150161
#endif

stdlib/public/core/ContiguousArrayBuffer.swift

+10-5
Original file line numberDiff line numberDiff line change
@@ -644,12 +644,17 @@ internal struct _ContiguousArrayBuffer<Element>: _ArrayBufferProtocol {
644644
return target + initializedCount
645645
}
646646

647-
public __consuming func _copyContents(
647+
@inlinable
648+
internal __consuming func _copyContents(
648649
initializing buffer: UnsafeMutableBufferPointer<Element>
649-
) -> (Iterator,UnsafeMutableBufferPointer<Element>.Index) {
650-
// This customization point is not implemented for internal types.
651-
// Accidentally calling it would be a catastrophic performance bug.
652-
fatalError("unsupported")
650+
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
651+
guard buffer.count > 0 else { return (makeIterator(), 0) }
652+
let c = Swift.min(self.count, buffer.count)
653+
buffer.baseAddress!.initialize(
654+
from: firstElementAddress,
655+
count: c)
656+
_fixLifetime(owner)
657+
return (IndexingIterator(_elements: self, _position: c), c)
653658
}
654659

655660
/// Returns a `_SliceBuffer` containing the given `bounds` of values

stdlib/public/core/SliceBuffer.swift

+11-5
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,18 @@ internal struct _SliceBuffer<Element>
245245
return target + c
246246
}
247247

248-
public __consuming func _copyContents(
248+
@inlinable
249+
internal __consuming func _copyContents(
249250
initializing buffer: UnsafeMutableBufferPointer<Element>
250-
) -> (Iterator,UnsafeMutableBufferPointer<Element>.Index) {
251-
// This customization point is not implemented for internal types.
252-
// Accidentally calling it would be a catastrophic performance bug.
253-
fatalError("unsupported")
251+
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
252+
_invariantCheck()
253+
guard buffer.count > 0 else { return (makeIterator(), 0) }
254+
let c = Swift.min(self.count, buffer.count)
255+
buffer.baseAddress!.initialize(
256+
from: firstElementAddress,
257+
count: c)
258+
_fixLifetime(owner)
259+
return (IndexingIterator(_elements: self, _position: startIndex + c), c)
254260
}
255261

256262
/// True, if the array is native and does not need a deferred type check.

test/stdlib/ArrayBridge.swift.gyb

+68-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -471,6 +471,8 @@ tests.test("testMutableArray") {
471471
}
472472

473473
tests.test("rdar://problem/27905230") {
474+
// Casting an NSArray to Array<Any> would trap because of an erroneous
475+
// precondition.
474476
let dict = RDar27905230.mutableDictionaryOfMutableLists()!
475477
let arr = dict["list"]!
476478
expectEqual(arr[0] as! NSNull, NSNull())
@@ -482,4 +484,69 @@ tests.test("rdar://problem/27905230") {
482484
expectEqual(arr[5] as! Date, Date(timeIntervalSince1970: 0))
483485
}
484486

487+
tests.test("verbatimBridged/Base/withUnsafeBufferPointer") {
488+
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
489+
let b = a as! [Base]
490+
let success: Bool = b.withUnsafeBufferPointer { buffer in
491+
expectEqual(buffer.count, 4)
492+
guard buffer.count == 4 else { return false }
493+
expectEqual(buffer[0].value, 0)
494+
expectEqual(buffer[1].value, 1)
495+
expectEqual(buffer[2].value, 2)
496+
expectEqual(buffer[3].value, 3)
497+
return true
498+
}
499+
expectTrue(success)
500+
}
501+
502+
// https://bugs.swift.org/browse/SR-14663
503+
tests.test("verbatimBridged/AnyObject/withUnsafeBufferPointer") {
504+
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
505+
let b = a as [AnyObject]
506+
let success: Bool = b.withUnsafeBufferPointer { buffer in
507+
expectEqual(buffer.count, 4)
508+
guard buffer.count == 4 else { return false }
509+
expectEqual((buffer[0] as? Base)?.value, 0)
510+
expectEqual((buffer[1] as? Base)?.value, 1)
511+
expectEqual((buffer[2] as? Base)?.value, 2)
512+
expectEqual((buffer[3] as? Base)?.value, 3)
513+
return true
514+
}
515+
expectTrue(success)
516+
}
517+
518+
tests.test("verbatimBridged/Base/withUnsafeMutableBufferPointer") {
519+
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
520+
var b = a as! [Base]
521+
let success: Bool = b.withUnsafeMutableBufferPointer { buffer in
522+
expectEqual(buffer.count, 4)
523+
guard buffer.count == 4 else { return false }
524+
expectEqual(buffer[0].value, 0)
525+
expectEqual(buffer[1].value, 1)
526+
expectEqual(buffer[2].value, 2)
527+
expectEqual(buffer[3].value, 3)
528+
buffer[0] = Base(4)
529+
return true
530+
}
531+
expectTrue(success)
532+
expectEqual(b[0].value, 4)
533+
}
534+
535+
tests.test("verbatimBridged/AnyObject/withUnsafeMutableBufferPointer") {
536+
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
537+
var b = a as [AnyObject]
538+
let success: Bool = b.withUnsafeMutableBufferPointer { buffer in
539+
expectEqual(buffer.count, 4)
540+
guard buffer.count == 4 else { return false }
541+
expectEqual((buffer[0] as? Base)?.value, 0)
542+
expectEqual((buffer[1] as? Base)?.value, 1)
543+
expectEqual((buffer[2] as? Base)?.value, 2)
544+
expectEqual((buffer[3] as? Base)?.value, 3)
545+
buffer[0] = Base(4)
546+
return true
547+
}
548+
expectTrue(success)
549+
expectEqual((b[0] as? Base)?.value, 4)
550+
}
551+
485552
runAllTests()
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//===--- ArrayBuffer_CopyContents.swift -----------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
// RUN: %target-run-simple-swift
14+
// REQUIRES: executable_test
15+
// REQUIRES: swift_stdlib_asserts
16+
17+
import Foundation
18+
import StdlibUnittest
19+
20+
let suite = TestSuite("ArrayBuffer_CopyContents")
21+
defer { runAllTests() }
22+
23+
24+
var trackedCount = 0
25+
var nextBaseSerialNumber = 0
26+
27+
/// A type that will be bridged verbatim to Objective-C
28+
class Thing: NSObject {
29+
var value: Int
30+
var serialNumber: Int
31+
32+
func foo() { }
33+
34+
required init(_ value: Int) {
35+
trackedCount += 1
36+
nextBaseSerialNumber += 1
37+
serialNumber = nextBaseSerialNumber
38+
self.value = value
39+
}
40+
41+
deinit {
42+
assert(serialNumber > 0, "double destruction!")
43+
trackedCount -= 1
44+
serialNumber = -serialNumber
45+
}
46+
47+
override func isEqual(_ other: Any?) -> Bool {
48+
return (other as? Thing)?.value == self.value
49+
}
50+
51+
override var hash: Int { value }
52+
}
53+
54+
55+
suite.test("nativeArray/_copyContents") {
56+
let array = [Thing(0), Thing(1), Thing(2), Thing(3)]
57+
expectEqualSequence(array._copyToNewArray(), array)
58+
}
59+
60+
suite.test("nativeArraySlice/_copyContents") {
61+
let array = (0 ..< 100).map { Thing($0) }
62+
expectEqualSequence(
63+
array[20 ..< 30]._copyToNewArray(),
64+
(20 ..< 30).map { Thing($0) })
65+
}
66+
67+
suite.test("bridgedArray/_copyContents") {
68+
let array = NSArray(array: (0 ..< 5).map { Thing($0) }) as! [Thing]
69+
expectEqualSequence(
70+
array._copyToNewArray(),
71+
(0 ..< 5).map { Thing($0) })
72+
}
73+
74+
suite.test("bridgedArraySlice/_copyContents") {
75+
let array = NSArray(array: (0 ..< 100).map { Thing($0) }) as! [Thing]
76+
expectEqualSequence(
77+
array[20 ..< 30]._copyToNewArray(),
78+
(20 ..< 30).map { Thing($0) })
79+
}

0 commit comments

Comments
 (0)