Skip to content

Commit aed05b9

Browse files
committed
Obj-C class metatypes should never satisfy Obj-C existentials
Given this ``` @objc protocol P { func f() } class C: P { func f() {} } ``` the casts `C.self is any P` and `C.self as? any P` should always fail, because the metatype of `C.self` does not have an `f()` implementation. These casts previously succeeded because the runtime implementation deferred to Obj-C's `[o conformsToProtocol:p]`, and that behaves differently depending on whether `o` is a class instance or a Class itself. Since these casts should never succeed, I've just modified the Swift runtime logic to fail whenever the source of the cast is an Obj-C Class and the target is a protocol existential. Resolves: rdar://106973771
1 parent 5b23b23 commit aed05b9

File tree

2 files changed

+32
-0
lines changed

2 files changed

+32
-0
lines changed

stdlib/public/runtime/SwiftObject.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,15 @@ id swift_dynamicCastObjCProtocolUnconditional(id object,
12721272
Protocol * const *protocols,
12731273
const char *filename,
12741274
unsigned line, unsigned column) {
1275+
if (numProtocols == 0) {
1276+
return object;
1277+
}
1278+
if (object_isClass(object)) {
1279+
// ObjC classes never conform to protocols
1280+
Class sourceType = object_getClass(object);
1281+
swift_dynamicCastFailure(sourceType, class_getName(sourceType),
1282+
protocols[0], protocol_getName(protocols[0]));
1283+
}
12751284
for (size_t i = 0; i < numProtocols; ++i) {
12761285
if (![object conformsToProtocol:protocols[i]]) {
12771286
Class sourceType = object_getClass(object);
@@ -1293,6 +1302,10 @@ id swift_dynamicCastObjCProtocolConditional(id object,
12931302
return nil;
12941303
}
12951304
}
1305+
if (object_isClass(object)) {
1306+
// ObjC classes never conform to protocols
1307+
return nil;
1308+
}
12961309
for (size_t i = 0; i < numProtocols; ++i) {
12971310
if (![object conformsToProtocol:protocols[i]]) {
12981311
return nil;

test/Casting/Casts.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,4 +1081,23 @@ CastsTests.test("type(of:) should look through __SwiftValue")
10811081
expectEqual(t, "S") // Fails: currently says `__SwiftValue`
10821082
}
10831083

1084+
#if _runtime(_ObjC)
1085+
@objc protocol P106973771 {
1086+
func sayHello()
1087+
}
1088+
CastsTests.test("Class metatype values should not cast to Obj-C existentials") {
1089+
class C106973771: NSObject, P106973771 {
1090+
func sayHello() { print("Hello") }
1091+
}
1092+
// A class instance clearly conforms to the protocol
1093+
expectTrue(C106973771() is any P106973771)
1094+
// But the metatype definitely does not
1095+
expectFalse(C106973771.self is any P106973771)
1096+
// The cast should not succeed
1097+
expectNil(C106973771.self as? any P106973771)
1098+
// The following will crash if the cast succeeds
1099+
(C106973771.self as? any P106973771)?.sayHello()
1100+
}
1101+
#endif
1102+
10841103
runAllTests()

0 commit comments

Comments
 (0)