I can use property_get methods on @objc marked variables in addition to "ivar_get" methods that I use on vars that are not obj-c (when property_get methods don't work). For the following class instance:
class MyClass {
@objc var foo: UInt8 = 0x11
@objc let foo2: Int8 = 0x22
var bar: UInt16 = 0x3333
let bar2: Int16 = -0x3333
var baz: UInt32 = 0x44444444
var baz2: Int32 = -0x44444444
var qux: UInt64 = 0x5555555555555555
var qux2: Int64 = -0x5555555555555555
let quux: (Int, UInt8) = (0x6666666666666666, 0x77)
}
let c = MyClass()
let someInt = unsafeBitCast(c, to: Int.self)
I'm now getting this auto-generated output:
class MyClass {
pad // 16 bytes, [98 c5 42 00 01 00 00 00 03 00 00 00 02 00 00 00]
var foo: UInt8 // 1 bytes, [11] 😁
let foo2: Int8 // 1 bytes, [22] 😁
var bar // 2 bytes, [33 33]
var bar2 // 2 bytes, [cd cc]
pad // 2 bytes, [00 00]
var baz // 4 bytes, [44 44 44 44]
var baz2 // 4 bytes, [bc bb bb bb]
var qux // 8 bytes, [55 55 55 55 55 55 55 55]
var qux2 // 8 bytes, [ab aa aa aa aa aa aa aa]
var quux // 9 bytes, [66 66 66 66 66 66 66 66 77]
pad // 7 bytes, [00 00 00 00 00 00 00]
}
Note that for "objc" marked variables I was able to decipher both "Int" vs "Uint" and "var" vs "let".
Why this was exposed:
ptrdiff_t ivar_getOffset(Ivar ivar) {
if (!ivar) return 0;
return *ivar->offset;
}
const char * ivar_getName(Ivar ivar) {
if (!ivar) return nil;
return ivar->name;
}
const char * ivar_getTypeEncoding(Ivar ivar) {
if (!ivar) return nil;
return ivar->type;
}
but not the "ivar_getSize" is somewhat beyond me. Without that call I can not show the above output reliably (e.g. for the "bar2" variable I'd have to show "4 bytes [33 33 cd cc]" instead of the correct "2 bytes [33 33]") or if I peek into those fields directly I'm indeed risking crashing on future (and past) OS systems.
Interesting fact: if the first 8 / 16 bytes of an arbitrary structure happen to be the bit representation of some class isa pointer plus low numbers (for retain count and whatever the other word is) - this memory would be treated by my code as a valid class instance
Example:
func test_bogus() {
let c = MyClass()
let object = unsafeBitCast(c, to: AnyObject.self)
let cls = object_getClass(object)!
let size = class_getInstanceSize(cls)
let block = malloc(size)!
let p = block.assumingMemoryBound(to: UInt8.self)
for i in 0 ..< size { p[i] = 0xAD }
memmove(block, unsafeBitCast(c, to: UnsafeRawPointer.self), 16) // copy the first 16 bytes
let address = unsafeBitCast(block, to: Int.self)
printObject(at: address)
}
Outputs:
class MyClass {
pad // 16 bytes, [98 85 1e 00 01 00 00 00 03 00 00 00 04 00 00 00]
var foo: UInt8 // 1 bytes, [ad]
let foo2: Int8 // 1 bytes, [ad]
var bar // 2 bytes, [ad ad]
var bar2 // 2 bytes, [ad ad]
pad // 2 bytes, [ad ad]
var baz // 4 bytes, [ad ad ad ad]
var baz2 // 4 bytes, [ad ad ad ad]
var qux // 8 bytes, [ad ad ad ad ad ad ad ad]
var qux2 // 8 bytes, [ad ad ad ad ad ad ad ad]
var quux // 9 bytes, [ad ad ad ad ad ad ad ad ad]
pad // 7 bytes, [ad ad ad ad ad ad ad]
}
as an extra precaution I could validate malloc_size and make sure it is not less (and not much greater) than the matching class instance size.