Soundness hole in isolated protocol conformances

I think you're right that as? is missing the check required by the SE, but I also think there's another few holes in the SE:

Protocols that extend Sendable shouldn't be eligible for isolated conformances, else:

import Dispatch

protocol P: Sendable {
    func f()
}

@MainActor
struct S: @MainActor P {
    func f() {
        MainActor.preconditionIsolated()
    }
}

@MainActor
func example(_ s: any Sendable) {
    let p = s as! any P // on the main actor, so the cast is allowed dynamically
    DispatchQueue.global().async {
        p.f() // oops, sent it anyway
    }
}

example(S())
dispatchMain()

But even without Sendable, you can still get into trouble:

import Dispatch

protocol P {
    func f()
}

@MainActor
struct S: @MainActor P {
    func f() {
        MainActor.preconditionIsolated()
    }
}

@MainActor
func example(_ s: sending Any) {
    let p = s as! any P // cast is dynamically OK
    Task.detached {
        p.f() // oops, sending
    }
}

example(S())
dispatchMain()

For the second example, I don't see a solution except to have casts join the result to the region of the casting code?

(@Douglas_Gregor)

3 Likes