Files
swift-mirror/test/Interpreter/SDK/Foundation_test.swift
Jordan Rose 425c190086 Restore initializing entry points for @objc convenience initializers (#21815)
This undoes some of Joe's work in 8665342 to add a guarantee: if an
@objc convenience initializer only calls other @objc initializers that
eventually call a designated initializer, it won't result in an extra
allocation. While Objective-C /allows/ returning a different object
from an initializer than the allocation you were given, doing so
doesn't play well with some very hairy implementation details of
compiled nib files (or NSCoding archives with cyclic references in
general).

This guarantee only applies to
(1) calling `self.init`
(2) where the delegated-to initializer is @objc
because convenience initializers must do dynamic dispatch when they
delegate, and Swift only stores allocating entry points for
initializers in a class's vtable. To dynamically find an initializing
entry point, ObjC dispatch must be used instead.

(It's worth noting that this patch does NOT check that the calling
initializer is a convenience initializer when deciding whether to use
ObjC dispatch for `self.init`. If we ever add peer delegation to
designated initializers, which is totally a valid feature, that should
use static dispatch and therefore should not go through objc_msgSend.)

This change doesn't /always/ result in fewer allocations; if the
delegated-to initializer ends up returning a different object after
all, the original allocation was wasted. Objective-C has the same
problem (one of the reasons why factory methods exist for things like
NSNumber and NSArray).

We do still get most of the benefits of Joe's original change. In
particular, vtables only ever contain allocating initializer entry
points, never the initializing ones, and never /both/ (which was a
thing that could happen with 'required' before).

rdar://problem/46823518
2019-01-14 13:06:50 -08:00

480 lines
15 KiB
Swift

// RUN: %target-run-simple-swift
// REQUIRES: executable_test
// REQUIRES: objc_interop
import Foundation
import StdlibUnittest
// rdar://problem/18884272
// Make sure that NSObject conforms to NSObjectProtocol. This
// particular bug is ridiculously hard to trigger without a complete
// SDK, so it sits here.
let objcProtocol: NSObjectProtocol = NSObject()
var FoundationTestSuite = TestSuite("Foundation")
func asNSString(_ s: String) -> NSString { return s as NSString }
func asString(_ ns: NSString) -> String { return ns as String }
//===----------------------------------------------------------------------===//
// Strings
//===----------------------------------------------------------------------===//
FoundationTestSuite.test("NSString") {
var str = "Hello"
var nsStr = str as NSString
assert(nsStr.compare(str).rawValue == ComparisonResult.orderedSame.rawValue)
assert(nsStr.compare(str) == ComparisonResult.orderedSame)
nsStr = "World"
str = nsStr as String
// FIXME: Shouldn't need coercion here to resolve ambiguity. <rdar://problem/14637688>
assert(str == asString(nsStr))
}
//===----------------------------------------------------------------------===//
// Numbers
//===----------------------------------------------------------------------===//
FoundationTestSuite.test("NSNumber") {
var i = 17
var d = 3.14159
var b = true
// Implicit boxing/explicit unboxing
var nsNum: NSNumber = i as NSNumber
expectEqual(i, Int(nsNum))
nsNum = d as NSNumber
expectEqual(d, Double(nsNum))
nsNum = b as NSNumber
expectEqual(b, Bool(nsNum))
// Literals
nsNum = 42
expectEqual(42, Int(nsNum))
nsNum = 3.14159
expectEqual(3.14159, Double(nsNum))
nsNum = false
expectEqual(false, Bool(nsNum))
}
//===----------------------------------------------------------------------===//
// Arrays
//===----------------------------------------------------------------------===//
FoundationTestSuite.test("NSArray") {
// Literals
var nsArr: NSArray = [1, 2.5, "Hello"]
assert(nsArr.count == 3)
// Subscripting
expectEqual(1, Int(nsArr[0] as! NSNumber))
expectEqual(2.5, Double(nsArr[1] as! NSNumber))
expectTrue((nsArr[2] as! NSString).isEqual("Hello"))
// Iteration
var result = [String]()
for x: Any in nsArr {
result.append((x as! NSObject).description)
}
expectEqualSequence(["1", "2.5", "Hello"], result)
}
FoundationTestSuite.test("NSMutableArray") {
let nsMutableArr: NSMutableArray = ["Constant", "Moon"]
nsMutableArr[0] = "Inconstant"
expectEqual(2, nsMutableArr.count)
expectEqual("Inconstant", nsMutableArr[0] as! NSString)
expectEqual("Moon", nsMutableArr[1] as! NSString)
}
FoundationTestSuite.test("NSArrayVariadicInit") {
let variadicArray = NSArray(objects: "A", "B", "C")
expectEqual(3, variadicArray.count)
}
FoundationTestSuite.test("arrayConversions") {
var nsa = NSArray()
var aoa: Array<AnyObject> = []
nsa as Array<AnyObject>
var nsa2 = NSArray()
var aoa2 = nsa2 as Array<AnyObject>
var nsaoa = aoa as NSArray
func nsArrayToAnyObjectArray(_ nsa: NSArray) -> [AnyObject] {
return nsa as [AnyObject]
}
nsArrayToAnyObjectArray(nsa)
nsArrayToAnyObjectArray(aoa as NSArray)
}
//===----------------------------------------------------------------------===//
// Dictionaries
//===----------------------------------------------------------------------===//
FoundationTestSuite.test("NSDictionary") {
var nsDict : NSDictionary = [1 : "Hello", 2 : "World"]
assert((nsDict[1]! as! NSString).isEqual("Hello"))
assert((nsDict[2]! as! NSString).isEqual("World"))
let nsMutableDict: NSMutableDictionary = ["Hello" : 1, "World" : 2 as NSNumber]
assert((nsMutableDict["Hello"]! as AnyObject).isEqual(1))
assert((nsMutableDict["World"]! as AnyObject).isEqual(2))
}
//===----------------------------------------------------------------------===//
// Ranges
//===----------------------------------------------------------------------===//
FoundationTestSuite.test("NSRange") {
let nsRange = NSRange(1..<5)
expectEqual("{1, 4}", String(NSStringFromRange(nsRange)))
}
FoundationTestSuite.test("RangeConversion") {
let i: Int8 = 10
let j: Int8 = 20
let nsrFromInt8 = NSRange(i..<j)
expectEqual(nsrFromInt8, NSRange(location: 10, length: 10))
var r = Range(nsrFromInt8)
expectNotNil(r)
expectEqual(r!.lowerBound, 10)
expectEqual(r!.upperBound, 20)
expectType(Optional<Range<Int>>.self, &r)
var r8 = Range<Int8>(nsrFromInt8)
expectNotNil(r8 != nil)
expectEqual(r8?.lowerBound, 10)
expectEqual(r8?.upperBound, 20)
expectType(Optional<Range<Int8>>.self, &r8)
var nsrFromPartial = NSRange(..<5)
expectEqual("{0, 5}", NSStringFromRange(nsrFromPartial))
let s = "Hello, 🌎!"
let b = s.firstIndex(of: ",")!
let e = s.firstIndex(of: "!")!
let nsr = NSRange(b..<e, in: s)
expectEqual(nsr.location, 5)
expectEqual(nsr.length, 4)
let rs = Range(nsr, in: s)!
expectEqual(s[rs], ", 🌎")
let nsrTo = NSRange(..<b, in: s)
expectEqual(nsrTo.location, 0)
expectEqual(nsrTo.length, 5)
let nsrFrom = NSRange(b..., in: s)
expectEqual(nsrFrom.location,5)
expectEqual(nsrFrom.length, 5)
expectNil(Range(NSRange(location: 100, length: 0), in: s))
expectNil(Range(NSRange(location: 0, length: 100), in: s))
let empty = ""
expectNil(Range(NSRange(location: 1, length: 0), in: empty))
expectNil(Range(NSRange(location: 0, length: 1), in: empty))
expectNotNil(Range(NSRange(location: 0, length: 0), in: empty))
// FIXME: enable once indices conform to RangeExpression
// let nsrFull = NSRange(s.indices, in: s)
// expectEqual(nsrFull.location, 0)
// expectEqual(nsrFull.length, 10)
}
//===----------------------------------------------------------------------===//
// URLs
//===----------------------------------------------------------------------===//
FoundationTestSuite.test("NSURL") {
let nsURL = NSURL(string: "http://llvm.org")!
expectEqual("http://llvm.org", nsURL.description)
}
//===----------------------------------------------------------------------===//
// Pattern-matching
//===----------------------------------------------------------------------===//
func matchesEither(_ input: NSNumber, _ a: NSNumber, _ b: NSNumber) -> Bool {
switch input {
case a, b:
return true
default:
return false
}
}
FoundationTestSuite.test("patternMatching") {
var one, two, three, oneAgain : NSNumber
one = NSNumber(value: 1)
two = NSNumber(value: 2)
three = NSNumber(value: 3)
oneAgain = NSNumber(value: 1)
expectFalse(matchesEither(one, two, three))
expectTrue(matchesEither(one, oneAgain, three))
expectTrue(matchesEither(one, two, oneAgain))
}
//===----------------------------------------------------------------------===//
// Miscellaneous
//===----------------------------------------------------------------------===//
// <rdar://problem/14474701>
// Type checker used to crash on this.
class ClassWithDtor : NSObject {
deinit {
let noteCenter = NotificationCenter.default
noteCenter.removeObserver(self, name: Notification.Name(rawValue: "ReceivedContentNotification"), object: nil)
}
}
FoundationTestSuite.test("rdar://17584531") {
// <rdar://problem/17584531>
// Type checker used to be confused by this.
var dict: NSDictionary = ["status": 200, "people": [["id": 255, "name": ["first": "John", "last": "Appleseed"] as NSDictionary] as NSDictionary] as NSArray]
var dict2 = dict["people"].flatMap { $0 as? NSArray }?[0] as! NSDictionary
expectEqual("Optional(255)", String(describing: dict2["id" as NSString]))
}
FoundationTestSuite.test("DarwinBoolean smoke test") {
expectFalse(CFEqual("abc" as NSString as CFString, "def" as NSString as CFString))
let _: CFArrayEqualCallBack = { DarwinBoolean($0 == $1) }
}
#if os(macOS)
FoundationTestSuite.test("NSRectEdge/constants") {
// Check that the following constants have the correct type and value.
//
// It is valid to hardcode the value in the test. The way they are currently
// defined in the SDK makes them ABI for Objective-C code.
expectEqual(0, CGRectEdge(rectEdge: NSRectEdge.minX).rawValue)
expectEqual(0, NSRectEdge(rectEdge: CGRectEdge.minXEdge).rawValue)
expectEqual(1, CGRectEdge(rectEdge: NSRectEdge.minY).rawValue)
expectEqual(1, NSRectEdge(rectEdge: CGRectEdge.minYEdge).rawValue)
expectEqual(2, CGRectEdge(rectEdge: NSRectEdge.maxX).rawValue)
expectEqual(2, NSRectEdge(rectEdge: CGRectEdge.maxXEdge).rawValue)
expectEqual(3, CGRectEdge(rectEdge: NSRectEdge.maxY).rawValue)
expectEqual(3, NSRectEdge(rectEdge: CGRectEdge.maxYEdge).rawValue)
}
#endif
if #available(OSX 10.11, iOS 9.0, *) {
FoundationTestSuite.test("UndoManager/ObjCClass") {
let UM = UndoManager()
// Confirm with an ObjC class.
class ObjCClass : NSObject {
var someProperty: String = ""
}
let f = ObjCClass()
UM.registerUndo(withTarget: f) { target in
target.someProperty = "expected"
}
UM.undo()
expectEqual(f.someProperty, "expected")
}
FoundationTestSuite.test("UndoManager/SwiftClass") {
let UM = UndoManager()
// Confirm with a Swift class.
class SwiftClass {
var someOtherProperty: String = ""
}
var b = SwiftClass()
UM.registerUndo(withTarget:b) { target in
target.someOtherProperty = "expected"
}
UM.undo()
expectEqual(b.someOtherProperty, "expected")
}
}
private let KEY = "some-key"
private func createTestArchive() -> NSData {
let mutableData = NSMutableData()
let KA = NSKeyedArchiver(forWritingWith: mutableData)
// Set up some fake data.
let obj = NSPredicate(value: true)
KA.encode(obj, forKey: KEY)
KA.encode(obj, forKey: NSKeyedArchiveRootObjectKey)
KA.finishEncoding()
return mutableData
}
FoundationTestSuite.test("NSKeyedUnarchiver/decodeObjectOfClass(_:forKey:)") {
let obj = NSPredicate(value: true)
let data = createTestArchive()
var KU = NSKeyedUnarchiver(forReadingWith: data as Data)
var missing = KU.decodeObject(of: NSPredicate.self, forKey: "Not there")
expectNil(missing)
expectType((NSPredicate?).self, &missing)
var decoded = KU.decodeObject(of: NSPredicate.self, forKey: KEY)
expectNotNil(decoded)
expectType((NSPredicate?).self, &decoded)
var wrongType = KU.decodeObject(of: DateFormatter.self, forKey: KEY)
expectNil(missing)
KU.finishDecoding()
}
if #available(OSX 10.11, iOS 9.0, *) {
FoundationTestSuite.test("NSKeyedUnarchiver/decodeTopLevel*") {
let obj = NSPredicate(value: true)
let data = createTestArchive()
// first confirm .decodeObjectWithClasses overlay requires an array of classes
var KU = NSKeyedUnarchiver(forReadingWith: data as Data)
var nonTopLevelResult = KU.decodeObject(of: [NSPredicate.self], forKey: KEY)
expectTrue(nonTopLevelResult != nil)
KU.finishDecoding()
KU = NSKeyedUnarchiver(forReadingWith: data as Data)
do {
// decodeObjectForKey(_:) throws
let decoded1 = try KU.decodeTopLevelObject(forKey: KEY) as? NSPredicate
let missing1 = try KU.decodeTopLevelObject(forKey:"Not there")
expectTrue(decoded1 != nil)
expectEqual(obj, decoded1)
expectTrue(missing1 == nil)
KU.finishDecoding()
// recreate so we can do the rest securely
KU = NSKeyedUnarchiver(forReadingWith: data as Data)
KU.requiresSecureCoding = true
// decodeObjectOfClass(_:,forKey:) throws
let decoded2 = try KU.decodeTopLevelObject(of: NSPredicate.self, forKey: KEY)
let missing2 = try KU.decodeTopLevelObject(of: NSPredicate.self, forKey: "Not there")
expectTrue(decoded2 != nil)
expectEqual(obj, decoded2)
expectTrue(missing2 == nil)
let classes: [AnyClass] = [NSPredicate.self]
let decoded3 = try KU.decodeTopLevelObject(of: classes, forKey: KEY) as? NSPredicate
let missing3 = try KU.decodeTopLevelObject(of: classes, forKey: "Not there")
expectTrue(decoded3 != nil)
expectEqual(obj, decoded3)
expectTrue(missing3 == nil)
}
catch {
expectTrue(false) // should have no errors
}
KU.finishDecoding()
KU = NSKeyedUnarchiver(forReadingWith: data as Data)
KU.requiresSecureCoding = true
do {
// recreate so we avoid caches from above
let wrong = try KU.decodeTopLevelObject(of: DateFormatter.self, forKey: KEY)
expectUnreachable() // should have error
}
catch {
// expected
}
KU.finishDecoding()
KU = NSKeyedUnarchiver(forReadingWith: data as Data)
KU.requiresSecureCoding = true
do {
let wrongClasses: [AnyClass] = [DateFormatter.self]
let wrong = try KU.decodeTopLevelObject(of: wrongClasses, forKey: KEY)
expectUnreachable() // should have error
}
catch {
// expected
}
KU.finishDecoding()
// confirm the that class function works
do {
let decoded = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data as Data) as? NSPredicate
expectEqual(obj, decoded)
}
catch {
expectUnreachableCatch(error)
}
}
FoundationTestSuite.test("NSKeyedUnarchiver/decodeTopLevelObjectOfClass(_:forKey:)/trap") {
let obj = NSPredicate(value: true)
let data = createTestArchive()
var KU = NSKeyedUnarchiver(forReadingWith: data as Data)
// Even though we're doing non-secure coding, this should still be checked
// in Swift.
do {
var wrongType = try KU.decodeTopLevelObject(of: DateFormatter.self, forKey: KEY)
expectType((DateFormatter?).self, &wrongType)
} catch {
expectUnreachableCatch(error)
}
KU.finishDecoding()
}
FoundationTestSuite.test("NSKeyedUnarchiver/CycleWithConvenienceInit") {
@objc(HasACyclicReference)
class HasACyclicReference: NSObject, NSCoding {
var ref: AnyObject?
override init() {
super.init()
ref = self
}
required convenience init?(coder: NSCoder) {
let decodedRef = coder.decodeObject(forKey: "ref") as AnyObject?
self.init(ref: decodedRef)
}
@objc init(ref: AnyObject?) {
self.ref = ref
super.init()
}
func encode(with coder: NSCoder) {
coder.encode(ref, forKey: "ref")
}
}
let data = NSKeyedArchiver.archivedData(withRootObject: HasACyclicReference())
let decodedObj = NSKeyedUnarchiver.unarchiveObject(with: data) as! HasACyclicReference
expectEqual(ObjectIdentifier(decodedObj), ObjectIdentifier(decodedObj.ref!),
"cycle was not preserved (due to object replacement?)")
}
}
FoundationTestSuite.test("NotificationCenter/addObserver(_:selector:name:object:)") {
let obj: AnyObject = "Hello" as NSString
NotificationCenter.default.addObserver(obj, selector: Selector("blah:"),
name: nil, object: nil)
let name = "hello"
NotificationCenter.default.addObserver(obj, selector: Selector("blarg:"),
name: Notification.Name(name),
object: nil)
}
runAllTests()