Usage of `UnsafeMutablePointer.init(mutating)`

I noticed this example from Implicit Casting and Bridging section in UnsafeRawPointer reference:

let intPointer: UnsafePointer<Int> = ...
print(address: intPointer, as: Int.self)
// Prints "42"

let mutableIntPointer = UnsafeMutablePointer(mutating: intPointer)
print(address: mutableIntPointer, as: Int.self)
// Prints "42"

So, this particular lines:

let intPointer: UnsafePointer<Int> = ...
let mutableIntPointer = UnsafeMutablePointer(mutating: intPointer)

makes me confused.

Does UnsafeMutablePointer.init(mutating:) work on any pointer, or do we need to guarantee mutability of the underlying address?

It works on any pointer, it's just usually the wrong thing to do. This is conceptually the same as "casting away the const" in C/C++: the language allows you to do it, but gosh-darn-it you'd better not mutate through that mutable pointer.

2 Likes

I'm trying to make a Swifty wrapper for sysctl, and I think I ran into a use-case for UnsafeMutablePointer.init(mutating:). My initial code requires keys be mutable, even though sysctl doesn't actually mutate them:

func getProcessInfo(for pid: pid_t) throws -> kinfo_proc {
    var keys = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid]
    
    var processInfo = kinfo_proc()
    var requiredSize = MemoryLayout.size(ofValue: processInfo)
    
    os_signpost(.begin, log: SignPosts.pointsOfInterestLog,
                name: "sysctl - get process info")
    let result = Darwin.sysctl(
        &keys, // <- The pointer in question
        UInt32(keys.count),
        &processInfo,
        &requiredSize,
        nil,
        0
    )
    os_signpost(.end, log: SignPosts.pointsOfInterestLog,
                name: "sysctl - get process info")
    
    guard result == 0 else { throw POSIXErrorCode(rawValue: errno).asError }
        
    guard requiredSize == MemoryLayout.size(ofValue: processInfo) else { throw PosixError.unknown }
    guard processInfo.kp_proc.p_pid == pid else { throw PosixError.unknown }
    
    return processInfo

}

I though I could replace &keys with UnsafeMutablePointer<Int32>(mutating: &keys), but that yields a warning:

Initialization of 'UnsafeMutablePointer' results in a dangling pointer

hich I think is because the lifetime of keys exists long enough for the call to UnsafeMutablePointer.init(mutating:), but not for the call to sysctl.

I suppose one alternative is:


func getProcessInfo(for pid: pid_t) throws -> kinfo_proc {
	let keys = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid]
	
	var processInfo = kinfo_proc()
	var requiredSize = MemoryLayout.size(ofValue: processInfo)
	
	try keys.withUnsafeBufferPointer { keysBuffer in
		os_signpost(.begin, log: SignPosts.pointsOfInterestLog,
					name: "sysctl - get process info")
		let result = Darwin.sysctl(
			UnsafeMutablePointer<Int32>(mutating: keysBuffer.baseAddress!),
			UInt32(keys.count),
			&processInfo,
			&requiredSize,
			nil,
			0
		)
		os_signpost(.end, log: SignPosts.pointsOfInterestLog,
					name: "sysctl - get process info")
		
		guard result == 0 else { throw POSIXErrorCode(rawValue: errno).asError }
			
		guard requiredSize == MemoryLayout.size(ofValue: processInfo) else { throw PosixError.unknown }
		guard processInfo.kp_proc.p_pid == pid else { throw PosixError.unknown }
	}
	
	return processInfo

}

Is there some function like withoutActuallyEscaping(_:do:), but like withoutActuallyMutating, with a sufficient lifetime to make a call like this, or is this code my best bet?

1 Like

Hmm, I can now see its use when bridged functions aren't properly annotated.

You're safe. Addresses to keyBuffer already has enough lifetime of the entire closure. Since the type is fixed, you might as well use .init for shorter text.

.init(mutating: keysBuffer.baseAddress!)
1 Like

That’s correct: the & operator only extends the pointer to the immediate call. This is part of why it should be distrusted, and why these warnings were added.

Yes, this is the best bet. It’s very sad, and you definitely want a comment above it that indicates that you’re 100% sure that sysctl does not mutate this buffer, because if it ever does you will get truly terrible-to-debug issues.

Hmmm well I don't really know that. I don't see anything in the documentation that guarentees that the name param, won't be modified, but I also don't see why it would. C is the wild west :confused:

Do you think I can guard that by doing something like this?

let  originalKeys = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid]
var keys = Array(originalKeys) // This forces a copy, right?

sysctl(...)

assert(originalKeys == keys)

But then I'm worried that the optimizer would outsmart me, and alias originalKeys and keys to the same buffer

If you don't much care whether keys changes, you can just make it var like you originally did. To answer the question, it does create a new array, but you can just do

var keys = originalKeys

It will reference the original value, but will get CoW-ed once you try to mutate it (and & counts as mutation).

If you look at the signatures for sysctlbyname, the name/key parameter is a const char*, a constant C-string. That's a pretty good indication that the name/key parameter is not changed by sysctl. If the routine was written today using something like C99 styles, it would probably be specified as "const int name", an unbounded const array of ints.

You have to really think about context when using low-level C interfaces, especially with the system level routines. Just a fact of life.

Note that if you use the withUnsafeMutableBytes call, or similar, they will require you to hold a var. As those methods are mutating, they will correctly ensure that your buffer is uniquely owned and therefore safe to be mutated by C.

Never considered this, but that makes total sense, glad it's taken care of for us.

For those academically interested in the "what happens if you get this wrong" story, if you use withUnsafeBytes then you are signalling to Swift that you will only read through that pointer. That allows Swift to avoid performing a copy-on-write, so you have a pointer to data that may be shared with other values in the program.

The usual outcome here is that you will silently rewrite some of those other values in the program. This breaks the value semantics of these types: other values in the program suddenly store data that was not there originally. Essentially, you have mutated immutable data. Usually this causes utterly baffling bugs where code that previously produced correct results produces incorrect ones, based entirely on what the rest of the program is doing.

1 Like