Why does this tuple -> String conversion trigger sporadic memory overlap exception

Viewed 99

I am working with some mixed C-Swift code that has a construction for converting a C-string in a struct to a Swift string like so:

struct MyCStruct {  // from the c header file
  char string[75];
}

extension MyCStruct {
  var swiftString: String {
    get { return String(cString: UnsafeRawPointer([self.string]).assumingMemoryBound(to: CChar.self)) }
  }
}

As of Swift 5.1, I started getting a sporadic memory overlap exception, possibly triggered by memory allocation elsewhere or a thread context switch. I was able to fix it by changing the get to:

get { return withUnsafePointer(to: self.string) { ptr -> String in
            return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self))
        } }

But, I am wondering is this just masking some deeper problem unrelated to the original construction? Or, is the original construction faulty, and just lucky it never crashed before?

1 Answers

Your method relies on undefined behavior. Here

UnsafeRawPointer([self.string])

a temporary array is created, and the address of its element storage is passed to the UnsafeRawPointer() initializer. That memory address is only valid for the duration of of the init call, but must not be used when the initializer returns.

What you can do is to get a pointer to the string variable itself (which is mapped as a tuple to Swift) and rebind that (compare Converting a C char array to a String):

extension MyCStruct {
    var swiftString: String {
        withUnsafePointer(to: self.string) {
            $0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: self.string)) {
                String(cString: $0)
            }
        }
    }
}

(It is assumed – as in your original code – that the C array of character is null-terminated.)

Related