New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-9603] NSFastEnumerationIterator doesn't detect mutations #3564
Comments
Also there's code in here that copies the struct properties to local variables, with a comment |
Another potential concern: if I have an |
The following seems like a much more faithful translation of the Obj-C version (completely untested of course): public struct NSFastEnumerationIterator : IteratorProtocol {
var enumerable: NSFastEnumeration
var objects: (Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?, Unmanaged<AnyObject>?) = (nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
var state = NSFastEnumerationState(state: 0, itemsPtr: nil, mutationsPtr: nil, extra: (0, 0, 0, 0, 0))
var index = 0
var count = 0
var mutationsPtr: UInt? = nil
public init(_ enumerable: NSFastEnumeration) {
self.enumerable = enumerable
}
public mutating func next() -> Any? {
if index >= count {
index = 0
count = withUnsafeMutablePointer(to: &self.objects) {
let buffer = AutoreleasingUnsafeMutablePointer<AnyObject?>($0)
return enumerable.countByEnumerating(with: &state, objects: buffer, count: 16)
}
if count == 0 { return nil }
if mutationsPtr == nil {
mutationsPtr = state.mutationsPtr.unsafelyUnwrapped.pointee
}
}
if state.mutationsPtr.unsafelyUnwrapped.pointee != mutationsPtr.unsafelyUnwrapped {
objc_enumerationMutation(enumerable)
}
defer { index += 1 }
return state.itemsPtr.unsafelyUnwrapped[index]
}
} |
Yeah, this looks a lot better. I assume The one reason I can think of to not always go through If we want to avoid the "calling |
This looks good to me, without having done a full comparison to the existing code. Thanks Lily! |
Environment
Verified with source inspection on both Swift 4.2.1 and latest Swift master.
Additional Detail from JIRA
md5: e8dc81adcdb9ed8602c6d95a9d12783b
Issue Description:
The implementation of
NSFastEnumerationIterator
doesn't detect mutations, which can lead to hard-to-debug crashes if the no-mutations invariant is violated.The current implementation initializes the
NSFastEnumerationState
such that themutationsPtr
is pointing at a known-zero value, and then it never touches themutationsPtr
again. This doesn't match the wayNSFastEnumeration
is used in Obj-C.Based on my reading of emitted LLVM IR, it appears that in Obj-C, a for-in loop desugars to the following code:
There's two major differences between this code and what Swift is doing:
The Swift code initializes
mutationsPtr
to a non-zero value, which seems to be pointless, and then never reads it, which means it can't detect mutations.For some reason the Swift code checks if the
itemsPtr
matches the stack-allocated array of objects, and if so, it then switches over the index and reads from that stack array. I see no reason for it to do this instead of just reading from theitemsPtr
always.The text was updated successfully, but these errors were encountered: