Warning: Initialization of 'UnsafeBufferPointer<T>' results in a dangling buffer pointer
Asked Answered
H

6

38

After update to Swift 5.2 / Xcode 11.4 got a warning to following code:

extension Data {

    init<T>(from value: T) {
        var value = value
        let pointer = UnsafeBufferPointer(start: &value, count: 1)
        self.init(buffer: pointer)
    }

    func to<T>(type: T.Type) -> T {
        return self.withUnsafeBytes { $0.load(as: T.self) }
    }
}

On line let pointer = UnsafeBufferPointer(start: &value, count: 1) I got

Initialization of 'UnsafeBufferPointer' results in a dangling buffer pointer

I can use @silenceWarning but it's dirty solution. Maybe I need to store pointer somewhere and clean it in the future?

Holster answered 25/3, 2020 at 21:38 Comment(2)
developer.apple.com/documentation/xcode_release_notes/… and search for danling. bugs.swift.org/browse/SR-2790 appears to have a more complete discussion of this.Connaught
to function Crashed (at $0.load(as: T.self) line), Thread 1: EXC_BAD_ACCESS (code=1, address=0x20) , XCODE 11.5 Swift5Melatonin
S
23

I also met these annoying warnings.

var str = "aaaaabbbbbccccc"
var num1 = 1
var num2 = 22

var data = Data()
// Initialization of 'UnsafeBufferPointer<String>' results in a dangling buffer pointer
data.append(UnsafeBufferPointer(start: &str, count: 1)) 
// Initialization of 'UnsafeBufferPointer<Int>' results in a dangling buffer pointer
data.append(UnsafeBufferPointer(start: &num1, count: 1))
// Initialization of 'UnsafeBufferPointer<Int>' results in a dangling buffer pointer 
data.append(UnsafeBufferPointer(start: &num2, count: 1)) 

Considering @greg's answer, I put the Data.append into withUnsafePointer's closure, and it does not show warnings anymore.

withUnsafePointer(to: &str) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } // ok
withUnsafePointer(to: &num1) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } // ok
withUnsafePointer(to: &num2) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } // ok

Here is the extension

extension Data {
    init<T>(value: T) {
        self = withUnsafePointer(to: value) { (ptr: UnsafePointer<T>) -> Data in
            return Data(buffer: UnsafeBufferPointer(start: ptr, count: 1))
        }
    }

    mutating func append<T>(value: T) {
        withUnsafePointer(to: value) { (ptr: UnsafePointer<T>) in
            append(UnsafeBufferPointer(start: ptr, count: 1))
        }
    }
}
Spaghetti answered 8/4, 2020 at 8:0 Comment(1)
DRY append(.init(value: value))Nuthouse
A
13

I had code which looked almost exactly what you were doing and was getting the same warning. Mine differed slightly in a way which is relevant to the discussion

init<T>(from value: T) {
    var value = value
    self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}

This still generates the warning that UnsafeBufferPointer is producing a dangling Pointer but the hints say "produces a pointer valid only for the duration of the call to 'init(start:count:)'"

But the return from UnsafeBufferPointer isn't assigned to anything, so I couldn't use it outside the scope of the init if I tried. So the compiler here is warning me against doing something I can't do anyway.

I guess Data.init(buffer: ) could be storing the ptr, but I would assume that if it accepts an UnsafeBufferPointer, it's accepting the responsibility for using it properly

Anyway, that still doesn't really fix your problem. I got around the warning with this

init<T>(from value: T) {
    var value = value
    var myData = Data()
    withUnsafePointer(to:&value, { (ptr: UnsafePointer<T>) -> Void in
        myData = Data( buffer: UnsafeBufferPointer(start: ptr, count: 1))
    })
    self.init(myData)
}

And this does not generate the warning and appears to work (in my application anyway). Whether it passes muster with the experts here is another matter.

Kind of makes me nostalgic for the days of HLock and HUnlock

Aorist answered 31/3, 2020 at 16:26 Comment(0)
A
5

This was never safe, so glad that the Swift team has cleaned it up:

let pointer = UnsafeBufferPointer(start: &value, count: 1)

At the end of this line of code, pointer is immediately invalid. There is not promise that value even exists at the next line of code. I'm not sure what you were trying to achieve here, but this was never a safe way to do it. What you're likely looking for is one of the .withUnsafeBytes methods, which depends on what you were working on.

Acrolith answered 25/3, 2020 at 21:43 Comment(3)
While your answer is probably correct, it would be much better if you showed an example of how this could fail. There are a few examples (https://mcmap.net/q/411191/-converting-a-c-char-array-to-a-string) of castings and conversions using Unsafe*Pointer floating around that now generate this warning.Connaught
You can probably test that by adding some "delays" using lock objects between pointer initialization and self.init call, which are used in another thread. Set a breakpoints so, that lock is occupied in another thread and that will delay the execution of this code. BTW, it also may not catch the case, I didn't test that.Gossamer
how is it "immediately invalid"? Looks perfectly valid as long as you control the life span of value. Pretty common for low level codingAbscind
G
4

Here is a correct solution:


extension Data {

    init<T>(from value: T) {
        // 1
        let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
        // 2
        pointer.initialize(to: value)
        defer {
            pointer.deinitialize(count: 1)
            pointer.deallocate()
        }
        // 3
        let bufferPointer = UnsafeBufferPointer(start: pointer, count: 1)
        self.init(buffer: bufferPointer)
    }

    func to<T>(type: T.Type) -> T {
        return self.withUnsafeBytes { $0.load(as: T.self) }
    }
}
  1. You allocate memory using UnsafeMutablePointer.allocate. The generic parameter lets Swift know you’re using the pointer to load and store values
  2. You must initialize typed memory before use and deinitialize it after use. You do this using the initialize and deinitialize methods, respectively.
  3. You convert the pointer to UnsafeBufferPointer. It points to safe memory until the end of the initializer.
Gossamer answered 26/2, 2022 at 16:14 Comment(0)
B
1

Found a nice answer here round trip Swift number types to/from Data

// value into Data
let data = withUnsafeBytes(of: value) { Data($0) }
// Data into value
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
Bodhisattva answered 15/5, 2020 at 0:41 Comment(0)
G
1

I my playground did it this way (according to this answer on the SO https://mcmap.net/q/188471/-round-trip-swift-number-types-to-from-data)

do {
  let array: [Float] = [1.2, 2.2]
  let data = withUnsafeBytes(of: array) { Data($0) }
  
  let restore: [Float] = data.withUnsafeBytes {
    $0.load(as: [Float].self)
  }
  
  array == restore //true
}

instead of [Float] you can use your type

Gilbertogilbertson answered 12/7, 2021 at 8:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.