I'm not telling you it fails, I'm telling you it's not safe. It's important to distinguish what the compiler does today from what the rules of the language are.
Concretely, bytes
is an "interior pointer": it is a pointer to data whose lifetime is managed by the lifetime of the NSData
object that owns it. If the NSData
object is deinit
ed, the data will be freed and the bytes
pointer will now be dangling.
In Swift, objects do not have lexical lifetimes: that is, the swift_release
call is not guaranteed to happen at the end of the block, but may happen any time after the point of last use of the object (see An unexpected deadlock with Combine only on Release build - #2 by lukasa for more). In general today the compiler is not good at taking advantage of this opportunity so it tends to be a bit lazy about when it actually issues a release call, but in principle this release can happen at any time after the point of last use.
So consider your function foo
:
func foo(data: Data) {
let count = data.count
let bytes = (data as NSData).bytes.assumingMemoryBound(to: UInt8.self)
for i in 0 ..< count {
myassert(bytes[i] == UInt8((i &* 12345) & 0xFF))
}
}
The moment the call to .bytes
completes, data
is never referenced again in this function. As a result, it would be entirely valid for the Swift compiler to transform your above function into:
func foo(data: Data) {
let count = data.count
let bytesTemporary = (data as NSData).bytes
swift_release(data) // deinit may occur here, making bytesTemporary immediately dangling
let bytes = bytesTemporary.assumingMemoryBound(to: UInt8.self)
for i in 0 ..< count {
myassert(bytes[i] == UInt8((i &* 12345) & 0xFF))
}
}
As you can see from the rewrite, bytes
is allowed to immediately become dangling the moment it's used.
The fact that you can't make this happen today actually makes this more dangerous, not less: it's much easier for you to accidentally introduce this bug into your code and not trip over it until months or years later, when the Swift compiler gets smart enough to release data
earlier and blow up your code.
Please please please never use NSData.bytes
in Swift code.