Stable pointers across withUnsafeMutablePointer(to:_:) invocations


(^) #1

If I have code like

class Stream 
{
    var stream:z_stream
}

var stream:Stream

mutating 
func foo1() 
{
    withUnsafeMutablePointer(to: &self.stream.stream) 
    {
        (storage:UnsafeMutablePointer<z_stream>) in 
        
        bar1(storage)
    }
}

mutating 
func foo2()
{
    withUnsafeMutablePointer(to: &self.stream.stream) 
    {
        (storage:UnsafeMutablePointer<z_stream>) in 
        
        bar2(storage)
}

do I have any guarantees about the location of storage between foo1 and foo2? the reason I’m asking is because zlib for some reason stores internally a pointer to something in the z_stream struct and it checks the consistency of this pointer in every library method. i already figured out a long time ago the z_stream has to be wrapped in a reference type to keep it from getting copied, and the class wrapper seems to fix everything for now, but is this defined behavior, especially since the z_stream itself is still a (C) struct?


(Jordan Rose) #2

I believe the inout-to-pointer conversion for a final stored class property gives you a consistent address each time, but otherwise it's not guaranteed. @Joe_Groff, @John_McCall, confirm/deny?


(Joe Groff) #3

You'll get a consistent identity, but you still shouldn't read or write the memory outside of a formal access.


(^) #4

everyone keeps talking about “& means copy-back semantics” and i always assumed that meant we have to assume this is taking place formally

var local:z_stream = self.stream.stream 
bar1(Builtin.address(of: local)) // whatever this builtin is called 
self.stream.stream = local

which is “supposed” to fail since the library pointer and passed pointer are no longer consistent


(^) #5

what’s a formal access?


(Joe Groff) #6

An access through a normal property read or write, inout-to-pointer call, or inside the confines of a withUnsafePointer/Bytes call. Even if you get the same pointer, code that reads or writes through the pointer outside of withUnsafe* may cause unpredictable behavior:

final class C { var x: Int }
let x = C()

var p: UnsafeRawPointer?
withUnsafeBytes(of: &x.x) {
  p = $0.startAddress

  print(p.load(as: Int.self)) // ok
}

print(p.load(as: Int.self)) // not ok

withUnsafeBytes(of: &x.x) {
  print(p.load(as: Int.self)) // probably ok
}

(^) #7

does that mean i have to allocate it manually if zlib is storing pointers to it internally?


(Joe Groff) #8

Yeah, or expand your withUnsafePointer scope to cover the duration of time zlib needs access to the memory.