I've got some code that I am migrating to Swift 5. I had thought this code was relatively safe, or as safe as I could make it considering it was using the Unsafe*
APIs. It turns out I was wrong and it was actually to be undefined behavior for Swift all along.
I have an extension to Data
called getFixedWidthInteger
which gets a FixedWidthInteger
from a specific index in the Data
. I used to use the now deprecated withUnsafeBytes
.
I tried the naïve migration:
extension Data {
func getFixedWidthInteger<I: FixedWidthInteger>(at index: Data.Index) -> I {
return self.withUnsafeBytes { $0.load(fromByteOffset: index, as: I.self) }
}
}
let data = Data([0x00, 0x00, 0x40, 0xA4, 0x81])
let uut: UInt32 = data.getFixedWidthInteger(at: 1) // Fatal error: load from misaligned raw pointer
XCTAssertEqual(uut, 2_175_025_152)
Which traps as expected. I've read multiple threads on here (e.g., Best practice for parsing heterogeneous types from Data (in Swift 5), Accessing a “misaligned” raw pointer safely, Built-in unaligned loads).
tl;dr: The Actual Question
I've come up with a solution that does not trap.
extension Data {
func getFixedWidthInteger<I: FixedWidthInteger>(at index: Data.Index) -> I {
return self.advanced(by: index).withUnsafeBytes { $0.load(fromByteOffset: 0, as: I.self) }
}
}
let data = Data([0x00, 0x00, 0x40, 0xA4, 0x81])
let uut: UInt32 = data.getFixedWidthInteger(at: 1)
XCTAssertEqual(uut, 2_175_025_152)
My guess is that it is not safe and I've just stumbled into a corner case that is not triggering an assertion. Maybe this is safe and completely defined behavior. However, given that I used to think my old code was safe and defined behavior and I was wrong I figured I'd rather ask and be sure.
How bad is this?