I recently fixed a bug in some code handing off a string to a C library function. The C function takes a plain char *
(no const
in either position): void use_str(char * s)
; so we need an UnsafeMutablePointer<CChar>
on the Swift side. Our Swift code was basically this:
import Foundation
let s = "abcde"
let sPtr = UnsafeMutablePointer(mutating: s.cString(using: .utf8))
use_str(sPtr)
Running this, if you inspect sPtr
and sPtr.pointee
before the use_str
call, you can see that the pointer is bad: it does not point to the contents of the string.* Until Xcode 10/Swift 4.2, it had consistently pointed to zeroed-out memory and the original implementer "worked around" what they thought was a bug in the C library. When we upgraded, the pointed-to values started being garbage, and there was breakage.
Storing the C string in a local variable first, var sChars = s.cString(using: .utf8)!
, fixes the issue. (That done, we can apparently also skip the explicit call to UnsafeMutablePointer(mutating:)
and just write use_str(&sChars)
.)
So it looks like the return value of cString(using:)
is invalid immediately unless explicitly copied. Shouldn't it live as long as the String
that produced it? If I understand correctly what's happening, this ObjC is equivalent to the original code:
NSString * s = @"abcde";
use_str([s cStringUsingEncoding:NSUTF8StringEncoding]);
which is perfectly valid, as far as I know (use_str
may need to copy the bytes, of course, but that's a separate issue).
Alternatively, can/should there be a warning about UnsafeMutablePointer(mutating:)
pointing directly to the result of a method call like this? Maybe our original Swift code is more equivalent instead to this, taking the address of a message send expression, which is illegal:
void update_char(char * c);
//...
update_char(&[s characterAtIndex:0]);
Please help me correct errors in my understanding; I'm trying to better grasp the situation/how Swift pointers operate. We should have caught this bug in our code, but I'm not sure why it occurred in the first place.
*In fact if you add another string/pointer pair, in Swift 4.2 the two pointers consistently hold the same address!