Bug
WatchConnectivitySession+WCSessionDelegate.swift (the didReceiveMessage and didReceiveMessageData bridges, around lines 116 and 170) builds a once-only reply guard like:
var replied = false
let safeReply: ([String: Any]) -> Void = { response in
guard !replied else { return }
replied = true
replyHandler(response)
}
let handler = unsafeBitCast(safeReply, to: ConnectivityHandler.self)
The unsafeBitCast launders a non-@Sendable closure with a captured mutable var replied into a @Sendable function type. The compiler would reject this capture in a real @Sendable closure — the cast hides a genuine data race: if the delegate's consumer invokes the handler from another thread while the bridge's auto-acknowledge path checks replied, the flag is read/written unsynchronized, and double-reply (undefined behavior on the sender side) is possible. Function-type unsafeBitCast is also unsupported-layout territory.
Impact today is limited — downstream AtLeast doesn't use the sendMessage path — but the bridge is public API.
Fix direction
Replace the cast with a properly @Sendable closure guarding the flag with a lock (OSAllocatedUnfairLock(initialState: false) or NSLock + captured reference box).
Bug
WatchConnectivitySession+WCSessionDelegate.swift(thedidReceiveMessageanddidReceiveMessageDatabridges, around lines 116 and 170) builds a once-only reply guard like:The
unsafeBitCastlaunders a non-@Sendableclosure with a captured mutablevar repliedinto a@Sendablefunction type. The compiler would reject this capture in a real@Sendableclosure — the cast hides a genuine data race: if the delegate's consumer invokes the handler from another thread while the bridge's auto-acknowledge path checksreplied, the flag is read/written unsynchronized, and double-reply (undefined behavior on the sender side) is possible. Function-typeunsafeBitCastis also unsupported-layout territory.Impact today is limited — downstream AtLeast doesn't use the
sendMessagepath — but the bridge is public API.Fix direction
Replace the cast with a properly
@Sendableclosure guarding the flag with a lock (OSAllocatedUnfairLock(initialState: false)orNSLock+ captured reference box).