Dangling pointer warning for class fields. Does it make sense?

Here is the code

class MyClass: UIViewController {
        typealias Me=MyClass
	private var conditionsChanged: Bool = false
	private var settingsChanged: Bool = false

       ......


	@objc private func onConfigLineClick(_ gr: NSGestureRecognizer) {
              guard let view = gr.view else {return}
              let param: ConfigParam
              let changedPtr: UnsafeMutablePointer<Bool>

              if let p = Me.paramFromLineView(view, conditions.allParams) {
			param = p
			changedPtr = UnsafeMutablePointer(&conditionsChanged)
		} else
		if let p = Me.paramFromLineView(view, settings.allParams) {
			param = p
			changedPtr = UnsafeMutablePointer(&settingsChanged)
		} else {
			return
		}

		if let iParam = param as? IntegerParam {
			iParam.nextValue()
		} else
		if let bParam = param as? BooleanParam {
			bParam.Value = !bParam.BoolValue
		} else {
			return
		}

		.....
		changedPtr.pointee = true
                ...
       }

}

Both assignments of UnsafeMutablePointer produce a warning with XCode 12: "Initialization of 'UnsafeMutablePointer' results in a dangling pointer".

Please, don't tell me how to rewrite the code to avoid the warning. This way it looks quite clear and efficient to me. What is really important, I don't see s reason for the warnings. Given that both settingsChanged and conditionsChanged are defined in the class, the pointer defined in a method can be dangling only in a weird case when the method frees its own class. Should the compiler really be so annoying?

I liked Swift for its flexibility of accessing pointer, which a language like Java does not offer. But Java allows to suppress specific warnings while with Swift I don't see such an option, while the "innovations" in Xcode 12 make me really frustrated.

I wonder if it is EVER possible (with XCode 12+) to assign a pointer explicitly (i.e. other than using 'withUnsafeBytes' etc) and avoid the warning.

I might be wrong, but I believe the issue here is that prefixing a value with & creates a pointer which is only guaranteed to be valid for the duration of the function which takes that value. So UnsafePointer(&myVariable) is not guaranteed to point to myVariable after UnsafePointer.init returns. If your code runs correctly, it is by chance and not guaranteed to do so (for instance after optimizations). So the example you provided can in fact be dangling if the class is still allocated (for instance if a temporary copy of the property was created for mutation).

1 Like

The compiler is right. Rather than trying to silence it, it might be worth trying to fix the problem it has.

As @George says, the & operator is essentially a shorthand for withUnsafeMutablePointer(to:). The closure part of that with function lasts only as long as the call site at which the & is present. After the end of that function call, the pointer is no longer valid and must not be used. Thus, if you call UnsafeMutablePointer(&whatever), the returned pointer is immediately invalid: the input pointer was only valid for the duration of the constructor.

Additionally, the code as written was never safe. There is no requirement that using the & operator on a stored class property points at its actual storage location. Swift is entitled to model any mutation to a stored property as a read/modify/write operation. Thus, getting a mutating pointer to class storage may involve copying the stored object out to a temporary location, vending a pointer to the temporary location, and then once the pointer is invalidated copying the data back.

The code as written is unsafe and the fact that it was working at all is sheer good luck.

Out of respect to your original request I won’t tell you how to rewrite the code, but the code is wrong and unsafe, and should be rewritten.

2 Likes

This is a common pitfall, sufficiently common that I wrote up my explanation on DevForums as … da da da! … The Peril of the Ampersand.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

3 Likes

I'm going to defiantly tell you how to rewrite it to avoid the warning, to help future readers with the same problem.

let changedKey: ReferenceWritableKeyPath<Me, Bool>
...
changedKey = \.conditionsChanged
...
changedKey = \.settingsChanged
...
self[keyPath: changedKey] = true

One circumstance where it is possible is when you allocate memory yourself:

let p = UnsafeMutablePointer<Bool>.allocate(capacity: 1)
let q: UnsafeMutablePointer<Bool> = p
3 Likes
Terms of Service

Privacy Policy

Cookie Policy