Overlapping access warning in withUnsafeMutableBytes

I would like to understand why this causes a compiler warning about overlapping access:

func foo() {
    var data = Data(count: 16)
    data.withUnsafeMutableBytes { ptr in
        ptr[0] = UInt8(data.count)
    }
    print(data)
}

Overlapping accesses to 'data', but modification requires exclusive access; consider copying to a local variable

Apparently this is caused by reading data.count inside the closure, changing the line to

        ptr[0] = UInt8(16)

makes it compile without warnings. But mutating data through the pointer obtained in withUnsafeMutableBytes will never change data.count. It this really a conflict that must be avoided?

Tested with Xcode 9.3 (9E145).

I think it has to do with the fact that withUnsafeMutableBytes assumes that you might change what's inside data, so it makes no assumptions about the exclusivity of the use of data inside of it.

Yes, but the compiler complains about reading the count (I have updated my question to clarify that). The closure can change the (currently allocated) content, but never the count.

withUnsafeMutableBytes(_:) is a mutating method on Data, therefore it requires write access to data for the duration of the call. By accessing data.count in the closure, you're starting a new read on data which conflicts with the current write access.

Even though in your particular case I don't believe there shouldn't be any problems with this code, as the mutation doesn't mutate the count; the exclusivity checking logic is conservative and assumes that any mutating method might mutate any part of the value it's called on, and therefore forbids the overlap.

You can solve the problem by first binding the count to a temporary:

func foo() {
  var data = Data(count: 16)
  let count = data.count
  data.withUnsafeMutableBytes { ptr in
    ptr[0] = UInt8(count)
  }
  print(data)
}

Now the read access for data.count happens before the write access for withUnsafeMutableBytes, so there's no conflict.

The fact that the diagnostic is a warning rather than an error appears to be due to a fix for a false negative where the compiler wouldn't look through reabstraction thunks (such as are required for generic functions to ensure the calling convention is passing generic values indirectly). This will become an error though (and is in the latest snapshots).

3 Likes

That's what I feared. Thanks for clearing it up and for the link (where this mentions exactly the same issue).

You could also capture a copy of data or its count in the closure's capture list:

data.withUnsafeMutableBytes {[data] ptr in
  ptr[0] = UInt8(data.count)
}

data.withUnsafeMutableBytes {[count = data.count] ptr in
  ptr[0] = UInt8(count)
}

The capture list gets evaluated and the results captured by value when the closure is formed, before the exclusive access begins, avoiding the overlapping access.

12 Likes

That is nice – thanks for the information!

Excellent, thanks for the explanation

Thank you. It resolved my issue.