Dereferencing the pointer is an "access":
_ = pointer.pointee // this is an access
A safe decoder should only access memory using a type that allows all valid bit patterns.
We don't formally distinguish between loading an aggregate as a whole vs reading a particular field within that aggregate. Although, naturally, the later is more likely break in practice.
We could think about different levels of correctness violations:
- API violation, but not UB: verification may fail
- Theoretical UB: no well-defined meaning
- Practical UB: will likely cause problems
struct Layout {
var code: UInt8
var flag: Bool
}
// Given `rawp: UnsafeRawPointer` that points to an unknown bit pattern:
let value = rawp.load(as: Layout.self) // UB, in theory
foo(value.flag) // Practical UB
let codePointer = rawp.bindMemory(to: UInt8.self, capacity: 1)
let code = codePointer.pointee // OK
// API violation because memory is bound to UInt8: verification can fail
let badLayoutPointer1 = rawp.assumingMemoryBound(to: Layout.self)
let badLayout1 = badLayoutPointer1.pointee // UB, in theory
foo(badLayout1.flag) // Practical UB
// OK to bind memory, regardless of the in-memory bitpattern
let badLayoutPointer2 = rawp.bindMemory(to: Layout.self, capacity: 1)
let badLayout = badLayoutPointer2.pointee // UB, in theory
foo(badLayout.flag) // Practical UB
Similary, zero is an invalid pointer value, so:
let rawp = unsafeBitCast(0, to: UnsafeRawPointer.self) // UB, in theory
return UnsafeRawPointer?(rawp) != nil // Practical UB