Jon_Shier
(Jon Shier)
1
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
lukasa
(Cory Benfield)
2
For the memcmp implementation, the common workarounds section of the proposal suggests withUnsafePointer(to:).
Jon_Shier
(Jon Shier)
3
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
}
}
}
lukasa
(Cory Benfield)
4
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
lukasa
(Cory Benfield)
5
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
}
Jon_Shier
(Jon Shier)
6
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)
}
}
}
eskimo
(Quinn “The Eskimo!”)
7
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
12 Likes