This data race safety hole was recently (re-)reported, and it's unclear to me where the responsibility for fixing it lies. Here's an illustration of the problem:
@MainActor
final class C {
var state = 0
}
func send(_ fn: sending @escaping () -> Void) {
// Can now call the closure (synchronously) from anywhere
// even though the one we passed was main-actor-isolated
Task.detached { fn() }
}
@MainActor
func bug(_ c: C) {
// The function type conversion here is allowed even though it loses
// isolation information and sends the closure value.
send { @MainActor in
c.state += 1
}
c.state += 1
}
Sema explicitly permits the conversion which drops the global actor – the original logic to support that was added here. There are a number of factors which control this, and the conversion is rejected in some cases, e.g. when the enclosing context doesn't match the isolation of the closure:
124 | func bug(_ c: C) {
125 | send { @MainActor in
| `- error: converting function value of type '@MainActor @Sendable () -> Void' to '() -> Void' loses global actor 'MainActor'
126 | c.state += 1
127 | }
The Sema logic also disallows the conversion if the resulting function type is Sendable. Converting to a non-Sendable function type and attempting to pass that along is tolerated in Sema, which delegates responsibility to region analysis to enforce concurrency safety. e.g. in the original example, this gets through Sema, but is caught during the SIL mandatory passes:
@MainActor
func bug(_ c: C) {
// Conversion is allowed in Sema
let fn: () -> Void = { @MainActor in
c.state += 1
}
send(fn) // 🛑
// |- error: sending 'fn' risks causing data races
// `- note: main actor-isolated 'fn' is passed as a 'sending' parameter; Uses in callee may race with later main actor-isolated uses
}
As the original example demonstrates, however, region analysis has its own blindspots, since the function value is allowed to be sent when the closure's isolation matches that of its enclosing context (which I think is the same as or related to the issues outlined here).
Which approach seems like the appropriate one to address this?
- Update Sema to prevent isolation-dropping conversions when converting values passed to
sendingparameters (and results?). This is an approach explored here. - Change region analysis so it will actually catch this sort of pattern.
- Both?
- Something else?