swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.69k stars 10.39k forks source link

[SR-11565] AnyObject allows invalid as? conversion of combined protocols #53970

Open rnapier opened 5 years ago

rnapier commented 5 years ago
Previous ID SR-11565
Radar rdar://problem/55987397
Original Reporter @rnapier
Type Bug
Environment Xcode 11.1 GM
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 1 | |Component/s | Compiler | |Labels | Bug, Miscompile, Runtime | |Assignee | None | |Priority | Medium | md5: 8b2c435e4403ed71b97f0b42fcc6b4d5

Issue Description:

protocol P {}

class C { let v = 1 }
class SubCP: C, P {}   // Just to show that some C's are P, but not all of them
class D: P {}

typealias AnyP = AnyObject & P  // Removing AnyObject resolves this issue
typealias CP = C & P            // Not all C are P, but CP is

let d = (D() as AnyP) as? CP    // This should fail; D is not C
d?.v    // Returns garbage value

See StackOverflow for another form of this issue.

This problem persists when P requires AnyObject directly.

protocol P: AnyObject {}

class C { let v = 1 }
class SubCP: C, P {}
class D: P {}

let d = (D() as P) as? C & P    // This should fail
d?.v    // Returns garbage value
belkadan commented 5 years ago

Yikes! Haven't seen this one before. Thanks, Rob.

@swift-ci create

swift-ci commented 5 years ago

Comment by Oleg Shanyuk (JIRA)

Hey! Just got pretty similar results using is operator:

runtime Xcode 10.3 + Playgrounds

class BasicObject {
    var a:Int = 0
}

protocol SuperNatural { }

class ChildObject0: BasicObject, SuperNatural {
    var b:Int = 0
}

class ChildObject1: BasicObject, SuperNatural {
    var c:Int = 0
}
// empty array vs non-empty array
let falselyDetectedArrayType = [ChildObject0]() // here's empty array of the required type
let correctlyDetectedArrayType = [ChildObject0()]  // pay attention → heres same type array with 1 element inside

let falselyCastArray = falselyDetectedArrayType as [SuperNatural]
let correctCastArray = correctlyDetectedArrayType as [SuperNatural]

// this line produces error
let arrayItemTypeFails = falselyCastArray is [ChildObject1] ? ChildObject1.self : ChildObject0.self
// this line works as expected
let arrayItemTypeSuccess = correctCastArray is [ChildObject1] ? ChildObject1.self : ChildObject0.self

print("type is \(arrayItemTypeFails)")
print("type is \(arrayItemTypeSuccess)")
print("type of falselyDetectedArrayType is \(type(of: falselyDetectedArrayType))")
print("type of falselyCastArray is \(type(of: falselyCastArray))")
print("type of correctCastArray is \(type(of: correctCastArray))")
print("test of direct type compare : \( [ChildObject0].self == [ChildObject1].self )")
// next line produces incorrect debugger warning also:
// WARNING text: Cast from '[ChildObject0]' to unrelated type '[ChildObject1]' always fails
print("test of type validation using <is> : \( [ChildObject0]() is [ChildObject1] )")

OUTPUT:

type is ChildObject1
type is ChildObject0
type of falselyDetectedArrayType is Array<ChildObject0>
type of falselyCastArray is Array<SuperNatural>
type of correctCastArray is Array<SuperNatural>
test of direct type compare : false
test of type validation using <is> : true
belkadan commented 5 years ago

Yep, is and as? are implemented the same way, so it's probably the same bug.

swift-ci commented 5 years ago

Comment by Oleg Shanyuk (JIRA)

@belkadan Thanks. Shall I create a new ticket, or this will go now in single train?

belkadan commented 5 years ago

I think it's fine to leave here. You've found another test case for the same issue.

mikeash commented 5 years ago

I see the bug using this test case with optimizations off, but it goes away when optimizations are on. Presumably that means the optimizer has the correct notion of subtyping here (yay!) but the runtime doesn't.

mikeash commented 5 years ago

Changed my mind: it looks like the compiler is only emitting code to check for `P`, not `C`. I've forwarded this along to the appropriate folks.