UnsafeMutablePointer and UnsafeMutableBufferPointer relationship confusion

I just wanted to make a post to provide a little bit of commentary on a lesson I learned while playing around in the unsafe parts of Swift.

I had some code that looked something like:

let typedPointer = UnsafeMutablePointer<CChar>.allocate(capacity: 1)
let channelReadBuffer = UnsafeMutableBufferPointer(start: typedPointer, count: 1024)
...

The Address Sanitizer caught and reported a heap-buffer-overflow issue. What I thought my code did was to make a pointer, typedPointer. And then provide that pointer to a UnsafeMutableBufferPointer which would then allocate 1024 CChars.

Based on the Address Sanitizer feedback that is wrong.

I think what I actually need to do is (and I'm still not 100% sure I am right):

// Allocate all 1024 CChar
let typedPointer = UnsafeMutablePointer<CChar>.allocate(capacity: 1024)
// Now allow the 1024 CChar to be treated as a Collection
let channelReadBuffer = UnsafeMutableBufferPointer(start: typedPointer, count: 1024)
...

I think that works because UnsafeMutableBufferPointer is really just adding the Collection conformance to UnsafeMutablePointer in this use case. This means that the UnsafeMutablePointer needs to be fully allocated before the UnsafeMutableBufferPointer "assumes control".

The part that was tripping me up was the fact that I was expected to specify the 1024 two times. In hind sight, and after multiple iterations of typing this up, it gradually became clear what the behavior actually was and why I was expected to specify the 1024 two times.

You can directly allocate as buffer.

let channedReadBuffer = UnsafeMutableBufferPointer<CChar>.allocate(capacity: 1024)
1 Like

Your understanding of what happened here is correct, but I will point out [@lantua beat me to it] that you can allocate the buffer directly with UnsafeMutableBufferPointer.allocate(capacity:) do this directly:

let channelReadBuffer = UnsafeMutableBufferPointer<CChar>.allocate(capacity: 1024)

(At this point channelReadBuffer is allocated but still uninitialized, so you will need to initialize it.)

I will say that it's really easy to miss this method because you'd likely look for something like this near the top of the documentation (near the usual initializers) but instead it's way far down past all of the instance methods under "Type Methods". I don't know if this is due to the default documentation sorting, but it might be worth overriding if possible. (@krilnon might know more)

I think that works because UnsafeMutableBufferPointer is really just adding the Collection conformance to UnsafeMutablePointer in this use case.

Put another way, UnsafeMutableBufferPointer.init(start:count:) allows you to statically express what you might otherwise know about the given start pointer: that it represents a contiguous region of memory of count instances of Element.

If you know this from the start, it's better to work with UnsafeMutableBufferPointer directly, but given an API which only returns a regular UnsafeMutablePointer, this is how you would convert, yes.

This means that the UnsafeMutablePointer needs to be fully allocated before the UnsafeMutableBufferPointer "assumes control".

Also correct — though in this case, it's better to work with a full buffer right from the get-go.

1 Like

Yeah, all of the task groups on that page are the automatically generated ones; the page could certainly benefit from some careful organization. :slightly_smiling_face:

1 Like

Re: UnsafeMutableBufferPointer.allocate(capacity:)

I did notice that method. The primary use of the channedReadBuffer memory was to hand off to a C API that took a UnsafeMutableRawPointer to the first address in the buffer. The secondary, higher level, uses were some of the Collection conformance that UnsafeMutableBufferPointer provides.

I could not find a way to go from UnsafeMutableBufferPointerUnsafeMutableRawPointer that I was happy with.

UnsafeMutableRawPointer(channelReadBuffer.baseAddress)!

So my "solution" was to make the pointer I wanted, typedPointer, and then turn that pointer into a buffer. No if let no force unwrapping.

None of that is to say my way is safer or better. Just trying to give a picture of why I chose what I did.

FWIW, I tend to do .init(channelReadBuffer.baseAddress!) when the pointer type can be inferred, like in function argument.