Stable pointer to a C String literal

Does Swift provide a way to store a C-string ( UnsafePointer<CChar> ) with a static lifetime? Something like a hypothetical:

let s: UnsafePointer<CChar> = "abc"

Where s has a stable pointer, pointing to a constant in the data section of the app's binary, just like a C string constant.


Context

I want to use kSMRightBlessPrivilegedHelper, which is declared as a C macro:

/*! 
 * @abstract
 * The authorization rights key for blessing and installing a privileged helper
 * tool. 
 */
#define kSMRightBlessPrivilegedHelper \
    "com.apple.ServiceManagement.blesshelper"

Since it's just a string literal in C, it has a constant address and you don't have to worry about alloc/freeing it.

But in Swift, it imports as Swift.String , and you have to worry about the lifetime of the scoped pointer you get with

kSMRightBlessPrivilegedHelper.withCString { thisDoesntOutliveTheClosure in
  // ...
}

I'd like to have the pointer outlive that closure scope. I'm okay with "leaking it", by having the pointer be allocated for the whole lifetime of the program (it's tiny)

smth dirty like this?

private var _helperString: UnsafeMutableRawPointer!

var helperString: UnsafeMutableRawPointer {
    if _helperString == nil {
        kSMRightBlessPrivilegedHelper.withCString { s in
            let len = strlen(s)
            _helperString = malloc(len + 1)!
            memmove(_helperString, s, len + 1)
        }
    }
    return _helperString!
}

Hah, I had something similar, but extracted into a helper function:

func copyIntoCString(_ str: UnsafePointer<CChar>) -> UnsafePointer<CChar> {
	let length = strlen(str)
	let result = UnsafeMutablePointer<CChar>.allocate(capacity: length+1)
	result.assign(from: str, count: length+1)
	return UnsafePointer(result)
}

let kSMRightBlessPrivilegedHelper_pointer = copyIntoCString(kSMRightBlessPrivilegedHelper)

It sounds like you might be looking for StaticString. It provides an unscoped .utf8Start pointer.

kSMRightBlessPrivilegedHelper is imported as a String though, not a StaticString.

If StaticString is not an option, you have to expose this pointer through another C function.

One comment: By being a macro, this line itself rely on linker to dedup all static string at link time. You probably want to modify the C side of things a bit anyway.

I had something similar

I just call strdup:

let kSMRightBlessPrivilegedHelper_pointer = strdup(kSMRightBlessPrivilegedHelper)

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

as in another example:

let str = "hello"
fputs(str, stdout)

can't you just pass the "swift" string?

func foo() {
    some_C_function(... kSMRightBlessPrivilegedHelper ...)
}

If I'm not mistaken, the pointer you get back from doing that is only guaranteed to be valid for the lifetime of the call of some_C_function. If some_C_function stashes it away somewhere, it'll be dangling.

very true, but i think such C api would be rather an exception than a rule and would be documented to have this behaviour, otherwise any code that does anything like this would break:

void foo() {
    char s[...];
    populate s
    some_C_function(s);
}

void bar() {
    char* s = malloc(...);
    populate s
    some_C_function(s);
    free(s);
}

One second look, the function I was concerned about doesn't have this problem after all! lol

i think such C api would be rather an exception than a rule

It does crop up pretty regularly, often when there’s some complicated set of structures that are set up and passed into a function call. One that I hit commonly is AuthorizationCopyRights.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like