The compiler is supposed to reject passing NonSendable
s across isolation domains. But when dealing with global isolated methods, it often fails.
class A {
func a() async { }
@MainActor
func b() async { }
// call isolated from nonisolated
func c() async {
await b() // <- ❗️❗️❗️ no compiler errors, not as expected
}
// call nonisolated from isolated
@MainActor
func d() async {
await a() // <- compiler emits an error, as expected
}
}
Both c
and d
are ill-formed, but the compiler can only detect errors in d
.
It is possible to construct an actual thread safe violation leveraging this vulnerability:
class Hell {
var value = 0
func a() async {
value += 100
await b()
}
@MainActor
func b() async {
Task {
for _ in 0..<100 {
value += 1
}
}
}
nonisolated func releaseDemon() async {
for _ in 0..<100 {
await a()
}
}
}
await Hell().releaseDemon()
The compiler has no problem with this code, but if we run it with thread sanitizer enabled, we can clearly see the contention on value
.
Edit: I can confirm this is another regression, Swift 5.10 was able to detect it.