App sometimes crashes when inserting String into Set with assertion ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS

Xcode downloaded a crash report for my app that crashed when trying to insert a String into a Set<String>. Apparently there was an assertion failure ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS. I assume that this assertion failure happened because the hash of the new element didn't match the hash of an equal already inserted element, but regardless, I don't understand how inserting a simple string could trigger this assertion.

Here is essentially the code that leads to the crash. path is any file system directory, and basePath is a directory higher in the hierarchy, or path itself.

var scanErrorPaths = Set<String>()

func main() {
    let path = "/path/to/directory"
    let basePath = "/path"
    let fileDescriptor = open(path, O_RDONLY)
    if fileDescriptor < 0 {
        if (try? URL(fileURLWithPath: path, isDirectory: false).checkResourceIsReachable()) == true {
            scanErrorPaths.insert(path.relativePath(from: basePath)!)
        return
    }
}

extension String {

    func relativePath(from basePath: String) -> String? {
        if basePath == "" {
            return self
        }
        guard let index = range(of: basePath, options: .anchored)?.upperBound else {
            return nil
        }
        return if index == endIndex || basePath == "/" {
            String(self[index...])
        } else if let index = self[index...].range(of: "/", options: .anchored)?.upperBound {
            String(self[index...])
        } else {
            nil
        }
    }

}

I miss something:

  • relativePath may return nil
  • you force unwrap, which could cause a crash
            scanErrorPaths.insert(path.relativePath(from: basePath)!)

Could you try replacing by:

        guard let index = range(of: basePath, options: .anchored)?.upperBound else {
            return ""
        }

and have relativePath return a String, not optional ?

In theory it could cause a crash, but in practice it shouldn't happen as one is always a subfile of the other.

I'm specifically interested about the crash I talked about, which is also shown in the crash report I attached at the end, and which I have no idea what it's caused by.

I notice the exception is on:

NativeSet.insertNew(_:at:isUnique:) + 376 (/:0)

Looking at this thread: https://forums.swift.org/t/substring-violates-hashables-requirements-stdlib-compiler-bug/58046/7

I wonder if the compiler is not confused and considering you try to insert a substring.

To make sure, could you try:

        let sub = self[index...] // a substring
        let theStr = String(sub)
        return if index == endIndex || basePath == "/" {
            theStr // String(self[index...]) // for sure, a String
        } else if let index = self[index...].range(of: "/", options: .anchored)?.upperBound {
            theStr // String(self[index...])
        } else {
            nil
        }

If that works, you should file a bug report.

I don't see what your code would fix or change, other than creating an extra variable. It also changes the program behaviour, because now the variable created in if let index is never used.

I should also specify that I was never able to reproduce this crash myself.

App sometimes crashes when inserting String into Set with assertion ELEMENT_TYPE_OF_SET_VIOLATES_HASHABLE_REQUIREMENTS
 
 
Q