UnsafeMutableRawPointer to UnsafeMutablePointer<T>: EXC_BAD_ACCESS on pointee

Viewed 972

I'm trying to get an UnsafeMutablePointer from an UnsafeMutableRawPointer obtained using Unmanaged.passUnretained().toOpaque():

class C { var foo = 42, bar = "bar" }
let c = C()

let rawPointer = Unmanaged.passUnretained(c).toOpaque()

let pointer = rawPointer.bindMemory(to: C.self, capacity: 1)
let pointee = pointer.pointee
print(pointee.foo) // EXC_BAD_ACCESS

Here's some LLDB output, which looks strange to me as everything seems alright in pointer until I ask for its pointee:

(lldb) frame variable -L c
scalar: (memtest2.C) c = 0x0000000101d00030 {
0x0000000101d00040:   foo = 42
0x0000000101d00048:   bar = "bar"
}
(lldb) frame variable -L rawPointer
0x00000001005e2e08: (UnsafeMutableRawPointer) rawPointer = {
scalar:   _rawValue = 0x0000000101d00030 {
0x0000000101d00040:     foo = 42
0x0000000101d00048:     bar = "bar"
  }
}
(lldb) frame variable -L pointer
0x00000001005e2e10: (UnsafeMutablePointer<memtest2.C>) pointer = 0x0000000101d00030
(lldb) frame variable -L pointer._rawValue
scalar: (memtest2.C) pointer._rawValue = 0x0000000101d00030 {
0x0000000101d00040:   foo = 42
0x0000000101d00048:   bar = "bar"
}
(lldb) frame variable -L pointee
0x00000001005e2e18: (memtest2.C) pointee = 0x00000001005b65d8 {
0x00000001005b65e8:   foo = 140736790071664
0x00000001005b65f0:   bar = ""
}

I've also tried assumingMemoryBound(to:), load(as:), or simply:

let pointer = UnsafePointer<C>(bitPattern: Int(bitPattern: rawPointer))!
print(pointer.pointee.foo) // EXC_BAD_ACCESS

But I always get this EXC_BAD_ACCESS error. What is going on here?

1 Answers

My rawPointer points to where the c instance data is located. It is not, as I expected, a pointer on the reference. Classes are reference types: the value of c is the memory address of where the class instance data is located. Yet the toOpaque() doc was clear:

Unsafely converts an unmanaged class reference to a pointer.

(toOpaque() actually calls unsafeBitCast(c, UnsafeMutableRawPointer.self))

To have a pointer on the reference one can simply do:

let referencePointer = UnsafeMutablePointer<C>(&c)

As my rawPointer points to the instance data, doing pointer.pointee tells the runtime that the first word of the instance data is a (or its) reference. Which of course isn't true nor makes sense.

Illustrating: (I've slightly change my initial code: both foo and bar are Int)

(lldb) frame variable c
(testmem.C) c = 0x0000000101833580 (foo = 42, bar = 84)

This (0x0000000101833580) is where the instance data is located. Let's see what the memory contains at this address:

(lldb) memory read 0x0000000101833580
0x101833580: e0 65 5b 00 01 00 00 00 02 00 00 00 00 00 00 00
0x101833590: 2a 00 00 00 00 00 00 00 54 00 00 00 00 00 00 00

I learned that the first word (e0 65 5b 00 01 00 00 00) is type data, the second (02 00 00 00 00 00 00 00) is reference counts (I don't know more about this), and the rest is the instance data. Indeed, 0x2a is 42 and 0x54 is 84. The value of foo and bar.

Doing pointer.pointee means telling the runtime that the first word (e0 65 5b 00 01 00 00 00 or 0x00000001005b65e0) is a reference pointing to where our instance data is located (which is obviously not the case)! Implying that pointer.pointee.foo is located at 0x00000001005b65e0 + 16 (0x00000001005b65f0) and bar at + 24 (0x00000001005b65f0).

(lldb) memory read 0x00000001005b65e0
0x1005b65e0: a9 65 5b 00 01 80 1d 00 80 62 5b 00 03 00 00 00
0x1005b65f0: 70 e9 10 9e ff 7f 00 00 00 00 00 00 00 00 00 00

foo contains 0x0000007fff9e10e970 which in decimal is 140735845296496 and corresponds to:

(lldb) frame variable -L pointee
0x00000001005e2e18: (testmem.C) pointee = 0x00000001005b65e0 {
0x00000001005b65f0:   foo = 140735845296496 // this
0x00000001005b65f8:   bar = 0
}

And as this data wasn't allocated by my program we don't have access to it, hence the EXC_BAD_ACCESS error.

Life makes sense now .

Related