mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Update for SE-0107: UnsafeRawPointer This adds a "mutating" initialize to UnsafePointer to make Immutable -> Mutable conversions explicit. These are quick fixes to stdlib, overlays, and test cases that are necessary in order to remove arbitrary UnsafePointer conversions. Many cases can be expressed better up by reworking the surrounding code, but we first need a working starting point.
493 lines
14 KiB
Plaintext
493 lines
14 KiB
Plaintext
%# -*- mode: swift -*-
|
|
//===--- NewArray.swift.gyb -----------------------------------*- swift -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
// RUN-DISABLED: %target-run-simple-swift | FileCheck %s
|
|
// RUN: rm -rf %t && mkdir -p %t && %gyb %s -o %t/NewArray.swift
|
|
// RUN: %line-directive %t/NewArray.swift -- %target-build-swift %t/NewArray.swift -o %t/a.out -Xfrontend -disable-access-control
|
|
// RUN: %target-run %t/a.out 2>&1 | %line-directive %t/NewArray.swift -- FileCheck %t/NewArray.swift --check-prefix=CHECK --check-prefix=CHECK-%target-runtime
|
|
// REQUIRES: executable_test
|
|
|
|
import StdlibUnittest
|
|
import StdlibCollectionUnittest
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
func printSequence<T : Sequence>(_ x: T) {
|
|
print("[", terminator: "")
|
|
var prefix = ""
|
|
for a in x {
|
|
print(prefix, terminator: "")
|
|
print(a, terminator: "")
|
|
prefix = ", "
|
|
}
|
|
print("]")
|
|
}
|
|
|
|
typealias BufferID = UnsafeRawPointer?
|
|
|
|
func bufferID<T : _ArrayProtocol>(_ x: T) -> BufferID {
|
|
return x._buffer.identity
|
|
}
|
|
|
|
func checkReallocation<T : _ArrayProtocol>(
|
|
_ x: T, _ lastBuffer: BufferID, _ reallocationExpected: Bool
|
|
) -> BufferID {
|
|
let currentBuffer = bufferID(x)
|
|
if (currentBuffer != lastBuffer) != reallocationExpected {
|
|
let message = reallocationExpected ? "lack of" : ""
|
|
print("unexpected \(message) reallocation")
|
|
}
|
|
return currentBuffer
|
|
}
|
|
|
|
func checkEqual<
|
|
S1 : Sequence, S2 : Sequence
|
|
where
|
|
S1.Iterator.Element == S2.Iterator.Element,
|
|
S1.Iterator.Element : Equatable
|
|
>(_ a1: S1, _ a2: S2, _ expected: Bool) {
|
|
if a1.elementsEqual(a2) != expected {
|
|
let un = expected ? "un" : ""
|
|
print("unexpectedly \(un)equal sequences!")
|
|
}
|
|
}
|
|
|
|
func test<
|
|
T : _ArrayProtocol
|
|
where
|
|
T.Iterator.Element == T._Buffer.Element,
|
|
T._Buffer.Element == T.Element,
|
|
T.Element == LifetimeTracked,
|
|
T.Index == Int,
|
|
T : RandomAccessCollection
|
|
>(_: T.Type, _ label: String) {
|
|
print("test: \(label)...", terminator: "")
|
|
|
|
var x: T = [
|
|
LifetimeTracked(1),
|
|
LifetimeTracked(2),
|
|
LifetimeTracked(3),
|
|
LifetimeTracked(4),
|
|
LifetimeTracked(5)
|
|
]
|
|
|
|
checkEqual(x, LifetimeTracked(1)...LifetimeTracked(5), true)
|
|
|
|
x.reserveCapacity(x.count + 2)
|
|
checkEqual(x, LifetimeTracked(1)...LifetimeTracked(5), true)
|
|
|
|
let bufferId0 = bufferID(x)
|
|
|
|
// Append a range of integers
|
|
x += LifetimeTracked(0)..<LifetimeTracked(2)
|
|
let bufferId1 = checkReallocation(x, bufferId0, false)
|
|
|
|
for i in x.count..<(x.capacity + 1) {
|
|
let bufferId1a = checkReallocation(x, bufferId1, false)
|
|
x.append(LifetimeTracked(13))
|
|
}
|
|
let bufferId2 = checkReallocation(x, bufferId1, true)
|
|
|
|
let y = x
|
|
x[x.index(before: x.endIndex)] = LifetimeTracked(17)
|
|
let bufferId3 = checkReallocation(x, bufferId2, true)
|
|
checkEqual(x, y, false)
|
|
|
|
func checkReallocations(
|
|
_ a: T, _ growthDescription: String, _ growBy1: (_: inout T) -> ()
|
|
) {
|
|
var a = a
|
|
var reallocations = 0
|
|
|
|
// Note: right now this test is dependent on a growth factor of 2.
|
|
// It's possible that factor will change, but (cursory) testing
|
|
// has shown that using 1.5, the other popular growth factor,
|
|
// slows things down.
|
|
for _ in a.count..<(a.capacity * 4) {
|
|
let oldId = bufferID(a)
|
|
growBy1(&a)
|
|
if oldId != bufferID(a) {
|
|
reallocations += 1
|
|
}
|
|
}
|
|
|
|
if reallocations > 3 {
|
|
print(
|
|
"Unexpectedly found \(reallocations) reallocations "
|
|
+ "of \(label) when growing via \(growthDescription)")
|
|
}
|
|
}
|
|
|
|
checkReallocations(x, "append") {
|
|
(x: inout T) -> () in x.append(LifetimeTracked(42))
|
|
}
|
|
checkReallocations(x, "+=") {
|
|
(x: inout T) -> () in x.append(LifetimeTracked(42))
|
|
}
|
|
print("done.")
|
|
}
|
|
|
|
print("testing...")
|
|
// CHECK: testing...
|
|
|
|
test(ContiguousArray<LifetimeTracked>.self, "ContiguousArray")
|
|
// CHECK-NEXT: test: ContiguousArray...done
|
|
|
|
|
|
test(Array<LifetimeTracked>.self, "Array")
|
|
// CHECK-NEXT: test: Array...done
|
|
|
|
test(ArraySlice<LifetimeTracked>.self, "ArraySlice")
|
|
// CHECK-NEXT: test: ArraySlice...done
|
|
|
|
func testAsArray() {
|
|
print("== AsArray ==")
|
|
var w: ContiguousArray<LifetimeTracked>
|
|
= [LifetimeTracked(4), LifetimeTracked(2), LifetimeTracked(1)]
|
|
// CHECK: == AsArray ==
|
|
|
|
let x = ContiguousArray(w)
|
|
print(bufferID(w) == bufferID(x))
|
|
// CHECK-NEXT: true
|
|
|
|
let y = Array(x)
|
|
print(bufferID(x) == bufferID(y))
|
|
// CHECK-NEXT: true
|
|
|
|
// Because of their indirection, arrays of classes can share
|
|
// buffers.
|
|
let y1 = Array(y)
|
|
print(bufferID(y1) == bufferID(y))
|
|
// CHECK-NEXT: true
|
|
|
|
let z = ArraySlice(y)
|
|
print(bufferID(y) == bufferID(z))
|
|
// CHECK-NEXT: true
|
|
|
|
w = ContiguousArray(z)
|
|
print(bufferID(w) == bufferID(z))
|
|
// CHECK-NEXT: true
|
|
|
|
let zz = y[0..<2]
|
|
print(bufferID(zz))
|
|
// CHECK-NEXT: 0x
|
|
}
|
|
testAsArray()
|
|
|
|
#if _runtime(_ObjC)
|
|
import Foundation
|
|
|
|
func nsArrayOfStrings() -> Array<NSString> {
|
|
let src: ContiguousArray<NSString> = ["foo", "bar", "baz"]
|
|
|
|
return src.withUnsafeBufferPointer {
|
|
let ns = NSArray(
|
|
objects: unsafeBitCast($0.baseAddress!,
|
|
to: UnsafeMutablePointer<AnyObject>.self),
|
|
count: $0.count)
|
|
return ns as! [NSString]
|
|
}
|
|
}
|
|
|
|
func testCocoa() {
|
|
print("== Cocoa ==")
|
|
// CHECK-objc: == Cocoa ==
|
|
|
|
var a = nsArrayOfStrings()
|
|
printSequence(a)
|
|
// CHECK-objc-NEXT: [foo, bar, baz]
|
|
|
|
a.append("qux")
|
|
printSequence(a)
|
|
// CHECK-objc-NEXT: [foo, bar, baz, qux]
|
|
|
|
a = nsArrayOfStrings()
|
|
printSequence(a)
|
|
// CHECK-objc-NEXT: [foo, bar, baz]
|
|
|
|
let b = a
|
|
|
|
a[1] = "garply"
|
|
printSequence(a)
|
|
// CHECK-objc-NEXT: [foo, garply, baz]
|
|
|
|
// Mutating an element in a has no effect on b
|
|
printSequence(b)
|
|
// CHECK-objc-NEXT: [foo, bar, baz]
|
|
|
|
a = nsArrayOfStrings()
|
|
a.insert("bag", at: 2)
|
|
printSequence(a)
|
|
// CHECK-objc-NEXT: [foo, bar, bag, baz]
|
|
|
|
a = nsArrayOfStrings()
|
|
a.reserveCapacity(30)
|
|
printSequence(a)
|
|
// CHECK-objc-NEXT: [foo, bar, baz]
|
|
|
|
print(a.capacity >= 30)
|
|
// CHECK-objc-NEXT: true
|
|
|
|
// Prove that we create contiguous storage for an opaque NSArray
|
|
a.withUnsafeBufferPointer {
|
|
(p) -> () in
|
|
print(p[0])
|
|
// CHECK-objc-NEXT: foo
|
|
}
|
|
}
|
|
testCocoa()
|
|
#endif // _runtime(_ObjC)
|
|
|
|
extension ArraySlice {
|
|
mutating func qsort(_ compare: (Element, Element) -> Bool) {
|
|
_introSort(&self, subRange: Range(self.indices), by: compare)
|
|
}
|
|
}
|
|
|
|
func testSlice() {
|
|
print("== ArraySlice ==")
|
|
// CHECK: == ArraySlice ==
|
|
|
|
// do some tests on the shared semantics
|
|
var b = ContiguousArray(LifetimeTracked(0)..<LifetimeTracked(10))
|
|
|
|
// ArraySlice it
|
|
var bSlice = b[3..<8]
|
|
print("<\(bSlice.count)>")
|
|
// CHECK-NEXT: <5>
|
|
print("bSlice0: \(bSlice)") // CHECK-NEXT: bSlice0: [3, 4, 5, 6, 7]
|
|
|
|
// bSlice += LifetimeTracked(11)..<LifetimeTracked(13)
|
|
|
|
// Writing into b does not change bSlice
|
|
b[4] = LifetimeTracked(41)
|
|
print("bSlice1: \(bSlice)") // CHECK-NEXT: bSlice1: [3, 4, 5, 6, 7]
|
|
|
|
// Writing into bSlice does not change b
|
|
bSlice[3] = LifetimeTracked(32)
|
|
|
|
print("bSlice2: \(bSlice)") // CHECK-NEXT: bSlice2: [32, 4, 5, 6, 7]
|
|
printSequence(b) // CHECK-NEXT: [0, 1, 2, 3, 41, 5, 6, 7, 8, 9]
|
|
|
|
let c = b
|
|
b[b.startIndex..<b.endIndex].qsort(<)
|
|
printSequence(b) // CHECK-NEXT: [0, 1, 2, 3, 5, 6, 7, 8, 9, 41]
|
|
printSequence(c) // CHECK-NEXT: [0, 1, 2, 3, 41, 5, 6, 7, 8, 9]
|
|
|
|
#if _runtime(_ObjC)
|
|
// Now a bridged slice
|
|
var a = Array<NSString>(
|
|
_ArrayBuffer(nsArray: nsArrayOfStrings()._buffer._asCocoaArray()))
|
|
|
|
printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz]
|
|
|
|
var aSlice = a[1..<3] // CHECK-objc-NEXT: [bar, baz]
|
|
printSequence(aSlice)
|
|
|
|
// Writing into aSlice works
|
|
aSlice[1] = "buzz" // CHECK-objc-NEXT: [buzz, baz]
|
|
printSequence(aSlice)
|
|
|
|
// ...and doesn't affect a
|
|
printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz]
|
|
|
|
// Appending to aSlice works...
|
|
aSlice.append("fodder")
|
|
print("<\(aSlice.count)>") // CHECK-objc-NEXT: <3>
|
|
printSequence(aSlice) // CHECK-objc-NEXT: [buzz, baz, fodder]
|
|
|
|
// And doesn't change a
|
|
printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz]
|
|
#endif
|
|
}
|
|
testSlice()
|
|
|
|
//===--- sub-range replacement --------------------------------------------===//
|
|
|
|
// Size of the array on which we're going to test "replace."
|
|
// testing time grows roughly as the cube of this constant
|
|
let testWidth = 11
|
|
|
|
%arrayTypes = ['ContiguousArray', 'Array', 'ArraySlice']
|
|
%for A in arrayTypes:
|
|
|
|
func testReplace(_ make: () -> ${A}<LifetimeTracked>) {
|
|
|
|
checkRangeReplaceable(
|
|
make, { LifetimeTracked(100)..<LifetimeTracked(100 + $0) })
|
|
}
|
|
|
|
func testReplace${A}(
|
|
makeOne: () -> ${A}<LifetimeTracked> = {
|
|
var x = ${A}<LifetimeTracked>()
|
|
// make sure some - but not all - replacements will have to grow the buffer
|
|
x.reserveCapacity(testWidth * 3 / 2)
|
|
x += LifetimeTracked(0)..<LifetimeTracked(testWidth)
|
|
return x
|
|
}
|
|
) {
|
|
testReplace(makeOne)
|
|
|
|
// Create one that will not be uniquely-referenced so we can test
|
|
// the out-of-place code paths.
|
|
let r = makeOne()
|
|
testReplace({ r })
|
|
|
|
// This test should ensure r's retain isn't dropped before we start testing.
|
|
if (r.count != testWidth) {
|
|
print("something bad happened!")
|
|
}
|
|
}
|
|
|
|
print("testing subrange replacement in ${A}")
|
|
testReplace${A}()
|
|
%end
|
|
|
|
// Also test with a sub-slice of some larger buffer. The "trailing"
|
|
// case is interesting because when the buffer is uniquely referenced
|
|
// we can expect to expand the slice in-place
|
|
for (maxValue, label) in [(testWidth, "trailing"), (testWidth*2, "interior")] {
|
|
print("testing subrange replacement in \(label) Sub-ArraySlice")
|
|
testReplaceArraySlice {
|
|
var a = ContiguousArray(LifetimeTracked(-testWidth)..<LifetimeTracked(maxValue))
|
|
a.reserveCapacity(a.count * 3 / 2)
|
|
return a[testWidth..<(2 * testWidth)]
|
|
}
|
|
}
|
|
|
|
// CHECK-NEXT: testing subrange replacement in ContiguousArray
|
|
// CHECK-NEXT: testing subrange replacement in Array
|
|
// CHECK-NEXT: testing subrange replacement in ArraySlice
|
|
// CHECK-NEXT: testing subrange replacement in trailing Sub-ArraySlice
|
|
// CHECK-NEXT: testing subrange replacement in interior Sub-ArraySlice
|
|
|
|
//===--- inout violations -------------------------------------------------===//
|
|
|
|
// The user has to obey certain rules when things are passed via
|
|
// inout, but in those cases we only guarantee memory-safety, not
|
|
// coherent semantics. Let's try to force a memory-safety problem
|
|
// here. This crashes when withUnsafeMutableBufferPointer is not
|
|
// sufficiently careful.
|
|
func testInoutViolation() {
|
|
var a: [LifetimeTracked] = [10, 8, 6, 4, 2, 0, 9, 7, 5, 3, 1].map { LifetimeTracked($0) }
|
|
|
|
%for A in arrayTypes:
|
|
do {
|
|
var b = ${A}(a)
|
|
_ = b.sorted { x, y in
|
|
b.removeAll()
|
|
return x < y
|
|
}
|
|
}
|
|
%end
|
|
|
|
// An overload of sorted for Arrays uses withUnsafeMutableBufferPointer,
|
|
// which disables bounds checks.
|
|
_ = a.sorted { x, y in
|
|
a = [] // Invalidate the whole array during sorting
|
|
return x < y
|
|
}
|
|
}
|
|
testInoutViolation()
|
|
|
|
//===--- single-element modifiers -----------------------------------------===//
|
|
|
|
%for A in arrayTypes:
|
|
|
|
func testSingleElementModifiers${A}() {
|
|
print("testing ${A} single-argument modifiers")
|
|
// CHECK-NEXT: testing ${A} single-argument modifiers
|
|
|
|
var a = ${A}(LifetimeTracked(0)..<LifetimeTracked(10))
|
|
print(a.removeLast().value) // CHECK-NEXT: 9
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
|
|
|
a.insert(LifetimeTracked(42), at: 4)
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 42, 4, 5, 6, 7, 8]
|
|
|
|
print(a.remove(at: 2).value) // CHECK-NEXT: 2
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 3, 42, 4, 5, 6, 7, 8]
|
|
}
|
|
testSingleElementModifiers${A}()
|
|
%end
|
|
|
|
//===--- isEmpty, first, last ---------------------------------------------===//
|
|
|
|
%for A in arrayTypes:
|
|
|
|
func testIsEmptyFirstLast${A}() {
|
|
print("testing ${A} isEmpty, first, and last")
|
|
// CHECK-NEXT: testing ${A} isEmpty, first, and last
|
|
|
|
print(${A}<Int>().isEmpty) // CHECK-NEXT: true
|
|
print(${A}(42...42).isEmpty) // CHECK-NEXT: false
|
|
|
|
print("<\(${A}(3..<43).first!)>") // CHECK-NEXT: <3>
|
|
print("<\(${A}(3..<43).last!)>") // CHECK-NEXT: <42>
|
|
|
|
print("<\(${A}<Int>().first)>") // CHECK-NEXT: nil
|
|
print("<\(${A}<Int>().last)>") // CHECK-NEXT: nil
|
|
|
|
var a = ${A}(LifetimeTracked(0)..<LifetimeTracked(10))
|
|
print(a.removeLast().value) // CHECK-NEXT: 9
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
|
|
|
a.insert(LifetimeTracked(42), at: 4)
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 42, 4, 5, 6, 7, 8]
|
|
|
|
print(a.remove(at: 2).value) // CHECK-NEXT: 2
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 3, 42, 4, 5, 6, 7, 8]
|
|
}
|
|
testIsEmptyFirstLast${A}()
|
|
%end
|
|
|
|
//===--- Regression Tests -------------------------------------------------===//
|
|
func rdar16958865() {
|
|
var a: [Int] = []
|
|
a += AnySequence([ 42, 4242 ])
|
|
// CHECK-NEXT: [42, 4242]
|
|
print(a)
|
|
}
|
|
rdar16958865()
|
|
|
|
#if _runtime(_ObjC)
|
|
#if os(OSX)
|
|
import AppKit
|
|
typealias OSColor = NSColor
|
|
#else
|
|
import UIKit
|
|
typealias OSColor = UIColor
|
|
#endif
|
|
|
|
class Rdar16914909 : NSObject {
|
|
var basicColorSet = [OSColor]()
|
|
|
|
func doColorStuff() {
|
|
basicColorSet.append(OSColor.lightGray())
|
|
print("appended")
|
|
}
|
|
}
|
|
|
|
Rdar16914909().doColorStuff()
|
|
// CHECK-objc-NEXT: appended
|
|
#endif
|
|
|
|
print("leaks = \(LifetimeTracked.instances)")
|
|
// CHECK-NEXT: leaks = 0
|
|
|
|
// CHECK-NEXT: all done.
|
|
print("all done.")
|
|
|
|
// ${'Local Variables'}:
|
|
// eval: (read-only-mode 1)
|
|
// End:
|