Search code examples
jsonswiftstructexc-bad-accessurlsession

EXC_BAD_ACCESS KERN_INVALID_ADDRESS on Struct Decodable.init(from:)


I have a function that can be run up to 2 times simultaneously to fetch information from an API. In Google Crashlytics, I see quite a few users affected by this crash but I've never seen anything like it and am not sure where to go from here.

The meat of the method:

URLSession.shared.dataTask(with: urlRequest) { (data, res, err) in
    guard let data = data else { return }
    do {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .formatted(self.dateFormatter)
        let json = try decoder.decode([String: TokenResponse].self, from: data)
    }
    catch {
        print(error.localizedDescription)
    }
}.resume()

TokenResponse:

struct TokenResponse: Decodable {
    var ticket : String
    var expiration : Date?
    var sessionId: String
}

Stack Trace:

Crashed: com.apple.NSURLSession-delegate
0  libicucore.A.dylib             0xe4874 icu::Calendar::clear() + 168
1  CoreFoundation                 0x57ab4 __cficu_ucal_clear + 28
2  CoreFoundation                 0x57ab4 __cficu_ucal_clear + 28
3  CoreFoundation                 0xca884 CFDateFormatterGetAbsoluteTimeFromString + 396
4  CoreFoundation                 0xe0490 CFDateFormatterCreateDateFromString + 108
5  Foundation                     0x1c2dc getObjectValue + 272
6  Foundation                     0x9f300 -[NSDateFormatter getObjectValue:forString:errorDescription:] + 200
7  Foundation                     0xbb5d0 -[NSDateFormatter dateFromString:] + 64
8  libswiftFoundation.dylib       0x37a58 __JSONDecoder.unbox(_:as:) + 592
9  libswiftFoundation.dylib       0x21064 __JSONDecoder.unbox_(_:as:) + 452
10 libswiftFoundation.dylib       0x1e040 _JSONKeyedDecodingContainer.decode<A>(_:forKey:) + 848
11 libswiftFoundation.dylib       0x27a48 protocol witness for KeyedDecodingContainerProtocol.decode<A>(_:forKey:) in conformance _JSONKeyedDecodingContainer<A> + 56
12 libswiftFoundation.dylib       0x258e0 protocol witness for KeyedDecodingContainerProtocol.decode<A>(_:forKey:) in conformance _JSONKeyedDecodingContainer<A> + 36
13 libswiftCore.dylib             0x549a8 KeyedDecodingContainerProtocol.decodeIfPresent<A>(_:forKey:) + 648
14 libswiftFoundation.dylib       0x231dc protocol witness for KeyedDecodingContainerProtocol.decodeIfPresent<A>(_:forKey:) in conformance _JSONKeyedDecodingContainer<A> + 36
15 libswiftCore.dylib             0x67dbc _KeyedDecodingContainerBox.decodeIfPresent<A, B>(_:forKey:) + 520
16 libswiftCore.dylib             0x55880 KeyedDecodingContainer.decodeIfPresent<A>(_:forKey:) + 76
17 MyApp                          0x1648dc MyClass.TokenResponse.init(from:) + 4344695004 (<compiler-generated>:4344695004)
18 MyApp                          0x164a78 protocol witness for Decodable.init(from:) in conformance MyClass.TokenResponse + 4344695416 (<compiler-generated>:4344695416)
19 libswiftCore.dylib             0x34c124 dispatch thunk of Decodable.init(from:) + 32
20 libswiftFoundation.dylib       0x51380 specialized __JSONDecoder.unbox<A>(_:as:) + 2540
21 libswiftFoundation.dylib       0x21048 __JSONDecoder.unbox_(_:as:) + 424
22 libswiftFoundation.dylib       0xcfc4 JSONDecoder.decode<A>(_:from:) + 1548
23 libswiftFoundation.dylib       0x44fc4 dispatch thunk of JSONDecoder.decode<A>(_:from:) + 56
24 MyApp                          0x12b84c closure #1 in MyClass.GetAllServerTokens() + 188 (MyClass.swift:188)
25 MyApp                          0x12add0 thunk for @escaping @callee_guaranteed (@guaranteed Data?, @guaranteed NSURLResponse?, @guaranteed Error?) -> () + 4344458704 (<compiler-generated>:4344458704)
26 CFNetwork                      0x22b34 CFURLRequestSetMainDocumentURL + 3028
27 CFNetwork                      0x33af8 _CFNetworkErrorCopyLocalizedDescriptionWithHostname + 11412
28 libdispatch.dylib              0x2914 _dispatch_call_block_and_release + 32
29 libdispatch.dylib              0x4660 _dispatch_client_callout + 20
30 libdispatch.dylib              0xbde4 _dispatch_lane_serial_drain + 672
31 libdispatch.dylib              0xc98c _dispatch_lane_invoke + 444
32 libdispatch.dylib              0x171a8 _dispatch_workloop_worker_thread + 656
33 libsystem_pthread.dylib        0x10f4 _pthread_wqthread + 288
34 libsystem_pthread.dylib        0xe94 start_wqthread + 8

EDIT: Example of the JSON that is fetched. I cannot post due to security concerns. It changes every 2 hours so I don't have the exact copy that caused the crashes but it should always follow this pattern. ticket and sessionId are never null:

{
    "1": {
        "ticket":"aRandomJSONWebTokenHere",
        "expiration":"2022-01-16T10:00:38.2775891Z",
        "sessionId":"aUuidHere"
    },
    "2": {
        "ticket":"aRandomJSONWebTokenHere",
        "expiration":"2022-01-16T10:00:38.2775891Z",
        "sessionId":"aUuidHere"
    }...all the way until 60
}

EDIT 2: self.dateFormatter:

let dateFormatter : DateFormatter = {
    let df = DateFormatter()
    df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"     
    return df
}()

self.dateFormatter is not modified. It is used in other places in the same manner, as a dateDecodingStrategy. Maybe this crash happens when more than one URLSession read self.dateFormatter at the same time?


Solution

  • Maybe this crash happens when more than one URLSession read self.dateFormatter at the same time?

    Sure, it's possible. Your code is certainly thread-unsafe! But it's obvious what to do; in your URLSession.shared.dataTask completion handler, get on the main thread and stay there.

    URLSession.shared.dataTask(with: urlRequest) { (data, res, err) in
        guard let data = data else { return }
        DispatchQueue.main.async {
            do { // ...
            }
            catch { // ...
            }
        }
    }.resume()