Search code examples
swiftfoundationnserror

NSError.setUserInfoValueProvider infinite loop


NSError.setUserInfoValueProvider(forDomain:provider:) was introduced in MacOS 10.11/iOS 9 as a way to populate the userInfo dictionary of an NSError for the given error domain using a block, thereby avoiding a lot of boilerplate and repetition in code that may throw.

I tried to use it like this:

if NSError.userInfoValueProvider(forDomain: "Test") == nil {
    NSError.setUserInfoValueProvider(forDomain: "Test"){ err, userInfoKey in
        print("This is an error:", err, userInfoKey)
        return nil
    }
}

The call site looks like this:

throw NSError(domain: "Test", code: 0, userInfo: nil)

When the error is thrown, the log is filled with "This is an error:", but the error itself or the userInfoKey is never printed out. The program finally aborts with a final message in the log:

warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.


Solution

  • The problem is that printing err in

    print("This is an error:", err, userInfoKey)
    

    calls the same value provider recursively, because the string representation of the error is determined. The program then eventually crashes with a stack overflow. You can verify that by setting a breakpoint on that line.

    If you change the line to

    print("This is an error:", userInfoKey)
    

    then everything works as expected.