Handling the new "Forming 'UnsafeRawPointer'..." warning

Swift 5.9 adds a new warning:

Forming 'UnsafeRawPointer' to a variable of type 'Type'; this is likely incorrect because 'Type' may contain an object reference.

This largely rears its head when bridging to C or Obj-C APIs. However, it's rather unclear what the intended resolution for the warning should be without fundamentally changing what the code does (and the PR doesn't help). For example, in Obj-C, it's typical to use a string as an associated object key.

extension ObjCType {
  private struct Keys { static var someKey = "someKey" }

  var isSomeBool: Bool {
    get { objc_getAssociatedObject(self, &Keys.someKey) as? Bool ?? false }
    set {
      objc_setAssociatedObject(self, &Keys.someKey, newValue, .OBJC_ASSOCIATION_COPY)
    }
  }
}

In this case it's possible to change the key type to something simple, like Bool, but that doesn't help if you're operating within an existing codebase and need compatibility with old code or SDKs. How would I keep compatibility here?

There are also examples where the underlying reference is actually important to the functionality of the code. For example, this OrderedSet comparison optimization from the Composable Architecture:

@inlinable
func areOrderedSetsDuplicates<T>(_ lhs: OrderedSet<T>, _ rhs: OrderedSet<T>) -> Bool {
  var lhs = lhs
  var rhs = rhs
  return memcmp(&lhs, &rhs, MemoryLayout<OrderedSet<T>>.size) == 0 || lhs == rhs
}

In this case, comparing the whole value, including references, is entirely intentional. How can we silence the warning while still using memcmp?

2 Likes

For the memcmp implementation, the common workarounds section of the proposal suggests withUnsafePointer(to:).

I'm not sure what you meant to link to there, but it links to a concurrency WWDC talk, which doesn't seem right.

As for the work around, I assume you mean that nesting withUnsafePointer(to:) would work?

@inlinable
func areOrderedSetsDuplicates<T>(_ lhs: OrderedSet<T>, _ rhs: OrderedSet<T>) -> Bool {
  withUnsafePointer(to: lhs) { lhs in
    withUnsafePointer(to: rhs) { rhs in
      var lhs = lhs
      var rhs = rhs
      return memcmp(&lhs, &rhs, MemoryLayout<OrderedSet<T>>.size) == 0 || lhs == rhs
    }
  }
}

Good call! I meant to link here.

Yes, though not exactly like that, as you're now comparing the byte values of the pointers. Drop the second pair of & and the inner shadow variables:

@inlinable
func areOrderedSetsDuplicates<T>(_ lhs: OrderedSet<T>, _ rhs: OrderedSet<T>) -> Bool {
  withUnsafePointer(to: lhs) { lhs in
    withUnsafePointer(to: rhs) { rhs in
      return memcmp(lhs, rhs, MemoryLayout<OrderedSet<T>>.size) == 0 || lhs == rhs
    }
  }
}
2 Likes

While I'm here I'll also note that the || lhs == rhs comparison in this context no longer works and has to get hoisted out:

@inlinable
func areOrderedSetsDuplicates<T>(_ lhs: OrderedSet<T>, _ rhs: OrderedSet<T>) -> Bool {
  withUnsafePointer(to: lhs) { lhs in
    withUnsafePointer(to: rhs) { rhs in
      return memcmp(lhs, rhs, MemoryLayout<OrderedSet<T>>.size) == 0
    }
  } || lhs == rhs
}

Thanks! And I see the proposal has an explicit workaround for the associated object case as well, so both questions are answered.

class Container {
  static var key = "key"

  func getObject() -> Any? {
    withUnsafePointer(to: Container.key) {
      return objc_getAssociatedObject(self, $0)
    }
  }
}

Given the ongoing travails associated with &, I always wondered why folks didn’t just switch to malloc:

class Container {
    static let key = malloc(1)!
    
    func getObject() -> Any? {
        return objc_getAssociatedObject(self, Container.key)
    }
}

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

8 Likes