Exception reason in Xcode Organizer

Usually when a crash is happening on iOS when the simulator or device is attached to Xcode I get a crash reason like the following example:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[NSJSONSerialization dataWithJSONObject:options:error:]: value parameter is nil'

When I look at the crashes of my published app in the Xcode Organizer using "Open in Project..." I see on which line of code the crash did happen. But is it also possible to get a reason for the crash like the above?

I'm asking this about Xcode Version 13.0.

Accepted Reply

The crash report is attached.

Oh, that’s very enlightening. Consider the backtrace of the crashing thread:

0   CoreFoundation  … __exceptionPreprocess + 220 …
1   libobjc.A.dylib … objc_exception_throw + 60 …
2   Foundation      … _writeJSONNumber + 1244 …
3   Foundation      … ___writeJSONObject_block_invoke + 388 …
4   CoreFoundation  … __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 24 …
5   CoreFoundation  … -[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:] + 184 …
6   Foundation      … _writeJSONObject + 508 …
7   Foundation      … ___writeJSONArray_block_invoke + 260 …
8   CoreFoundation  … __NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__ + 24 …
9   CoreFoundation  … -[__NSArrayM enumerateObjectsWithOptions:usingBlock:] + 196 …
10  Foundation      … _writeJSONArray + 340 …
11  Foundation      … ___writeJSONObject_block_invoke + 388 …
12  CoreFoundation  … __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 24 …
13  CoreFoundation  … -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 208 …
14  Foundation      … _writeJSONObject + 508 …
15  Foundation      … -[_NSJSONWriter dataWithRootObject:options:] + 88 …
16  Foundation      … +[NSJSONSerialization dataWithJSONObject:options:error:] + 120 …
17  Rrrr            … -[LocationManager sendPendingLocationObjects:] + 1988 

The exception isn’t coming directly from +dataWithJSONObject:options:error: but from something much deeper, namely _writeJSONNumber. Also, the pattern in the backtrace gives you a rough idea of the nesting, namely dictionary > array > dictionary > number. Finally, disassembling _writeJSONNumber (see below) reveals three potential causes:

  • Invalid number value (NaN) in JSON write
  • Invalid number value (infinite) in JSON write
  • Invalid number type in JSON write (%c)

I suspect it’s one of the first two.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

(lldb) disas -n _writeJSONNumber
Foundation`_writeJSONNumber:
    0x1826fa5bc <+0>:    pacibsp 
…
    0x1826fa9e0 <+1060>: brk    #0x1

    0x1826fa9e4 <+1064>: adrp   x8, 348530
    0x1826fa9e8 <+1068>: ldr    x0, [x8, #0x670]
    0x1826fa9ec <+1072>: adrp   x8, 358092
    0x1826fa9f0 <+1076>: ldr    x8, [x8, #0xe70]
    0x1826fa9f4 <+1080>: ldr    x2, [x8]
    0x1826fa9f8 <+1084>: adrp   x8, 297027
    0x1826fa9fc <+1088>: add    x1, x8, #0x8d1            ; =0x8d1 
    0x1826faa00 <+1092>: adrp   x3, 364346
    0x1826faa04 <+1096>: add    x3, x3, #0x768            ; =0x768 

(lldb) po 0x1826fa000+(364346<<12)+0x768
Invalid number value (NaN) in JSON write

    0x1826faa08 <+1100>: b      0x1826faa8c               ; <+1232>

    0x1826faa0c <+1104>: adrp   x8, 348530
    0x1826faa10 <+1108>: ldr    x0, [x8, #0x670]
    0x1826faa14 <+1112>: adrp   x8, 358092
    0x1826faa18 <+1116>: ldr    x8, [x8, #0xe70]
    0x1826faa1c <+1120>: ldr    x2, [x8]
    0x1826faa20 <+1124>: adrp   x8, 297027
    0x1826faa24 <+1128>: add    x1, x8, #0x8d1            ; =0x8d1 
    0x1826faa28 <+1132>: adrp   x3, 364346
    0x1826faa2c <+1136>: add    x3, x3, #0x788            ; =0x788 

(lldb) po 0x1826fa000+(364346<<12)+0x788
Invalid number value (infinite) in JSON write

    0x1826faa30 <+1140>: b      0x1826faa8c               ; <+1232>

    0x1826faa34 <+1144>: adrp   x8, 348530
    0x1826faa38 <+1148>: ldr    x19, [x8, #0x620]
    0x1826faa3c <+1152>: mov    x0, x20
    0x1826faa40 <+1156>: mov    x1, x22
    0x1826faa44 <+1160>: bl     0x180bc7e58     QQQ objc_msgSend
    0x1826faa48 <+1164>: ldrsb  x8, [x0]
    0x1826faa4c <+1168>: adrp   x9, 348522
    0x1826faa50 <+1172>: ldr    x1, [x9, #0x980]

(lldb) p *(char **)(0x1826fa000+(348522<<12)+0x980)
(char *) $5 = 0x00000001caf3a2e2 "stringWithFormat:"

    0x1826faa54 <+1176>: str    x8, [sp, #-0x10]!
    0x1826faa58 <+1180>: adrp   x2, 364346
    0x1826faa5c <+1184>: add    x2, x2, #0x7a8            ; =0x7a8 

(lldb) po 0x1826fa000+(364346<<12)+0x7a8
Invalid number type in JSON write (%c)

    0x1826faa60 <+1188>: mov    x0, x19
    0x1826faa64 <+1192>: bl     0x180bc7e58     QQQ objc_msgSend
    0x1826faa68 <+1196>: add    sp, sp, #0x10             ; =0x10 

    0x1826faa6c <+1200>: mov    x3, x0
    0x1826faa70 <+1204>: adrp   x8, 348530
    0x1826faa74 <+1208>: ldr    x0, [x8, #0x670]
    0x1826faa78 <+1212>: adrp   x8, 358092
    0x1826faa7c <+1216>: ldr    x8, [x8, #0xe70]
    0x1826faa80 <+1220>: ldr    x2, [x8]
    0x1826faa84 <+1224>: adrp   x8, 297027
    0x1826faa88 <+1228>: add    x1, x8, #0x8d1            ; =0x8d1 

(lldb) p (char *)(0x1826fa000+(297027<<12)+0x8d1)
(char *) $3 = 0x00000001caf3d8d1 "exceptionWithName:reason:userInfo:"

    0x1826faa8c <+1232>: mov    x4, #0x0
    0x1826faa90 <+1236>: bl     0x180bc7e58

(lldb) disas -c 3 -s 0x180bc7e58
    0x180bc7e58: adrp   x16, 100442
    0x180bc7e5c: add    x16, x16, #0x5c0          ; =0x5c0 
    0x180bc7e60: br     x16
(lldb) p/a 0x180bc7000+(100442<<12)+0x5c0
(long) $0 = 0x00000001994215c0 libobjc.A.dylib`objc_msgSend

    0x1826faa94 <+1240>: bl     0x181031af4               ; symbol stub for: objc_exception_throw

Replies

This exception means that you’ve passed nil to the obj parameter of that method. For example, this code will trigger it:

id obj = nil;
NSData * d = [NSJSONSerialization dataWithJSONObject:obj options:0 error:NULL];

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks, but I know that already. What I was trying to ask is something different:

When I look at the crashes of my published app in the Xcode Organizer by selecting a crash and then using "Open in Project..." I see on which line of code the crash did happen. But is it also possible for those crashed to get a "terminating app due to..." reason?

What I was trying to ask is something different:

Thanks for the clarification.

It’s easy to recognise when an app crashes due to an unhandled language exception. Specifically:

  • The Exception Type is EXC_CRASH (SIGABRT).

  • There’s a Last Exception Backtrace section.

See the discussion of this in Identifying the Cause of Common Crashes.

In this case the exception message is logged to the system log but it is not captured by the crash report. If you’re working with a user who is having a problem like this, you can ask them to send you a sysdiagnose log taken shortly after the crash. That’ll include the system log and hence the exception message.

If not, things get trickier. There’s a couple of things you can do:

  • If, as in the case with your +[NSJSONSerialization dataWithJSONObject:…] example, there’s only a few likely candidates for the problem, you can write code that triggers the exception and run it locally. You can then map the offset within the function of your crash to the same offset in your crash report. If they match then you know that the exception message you’re seeing also applies to your crash report.

  • If there aren’t any obvious candidates then the next step is to disassemble the crashed routine to see how the exception message is generated. I’ve included an example of this below.

The biggest drawback with these techniques is that they require you run on the same OS release as the user who hit the crash report. You can match that up using the UUID in the Binary Images section of the crash report.


Consider this snippet for the Last Exception Backtrace:

0 CoreFoundation  … __exceptionPreprocess + 220
1 libobjc.A.dylib … objc_exception_throw + 59
2 Foundation      … +[NSJSONSerialization dataWithJSONObject:options:error:] + 315
3 MyApp           … -[MainViewController tableView:didSelectRowAtIndexPath:] + 26728 (MainViewController.m:17)

Which exception is being thrown in frame 2? If you disassemble the routine you see this:

(lldb) disas -n '+[NSJSONSerialization dataWithJSONObject:options:error:]'
Foundation`+[NSJSONSerialization dataWithJSONObject:options:error:]:
    0x1907914e0 <+0>:   pacibsp 
    0x1907914e4 <+4>:   sub    sp, sp, #0x50             ; =0x50 
    0x1907914e8 <+8>:   stp    x24, x23, [sp, #0x10]
    0x1907914ec <+12>:  stp    x22, x21, [sp, #0x20]
    0x1907914f0 <+16>:  stp    x20, x19, [sp, #0x30]
    0x1907914f4 <+20>:  stp    x29, x30, [sp, #0x40]
    0x1907914f8 <+24>:  add    x29, sp, #0x40            ; =0x40 
    0x1907914fc <+28>:  mov    x22, x1
    0x190791500 <+32>:  mov    x23, x0
    0x190791504 <+36>:  cbz    x2, 0x190791594           ; <+180>
    0x190791508 <+40>:  mov    x19, x4
    0x19079150c <+44>:  mov    x20, x3
    0x190791510 <+48>:  mov    x21, x2
    0x190791514 <+52>:  tbnz   w20, #0x2, 0x190791530    ; <+80>
    0x190791518 <+56>:  mov    x0, x21
    0x19079151c <+60>:  bl     0x18f4db7f0               ; _NSIsNSArray
    0x190791520 <+64>:  tbnz   w0, #0x0, 0x190791530     ; <+80>
    0x190791524 <+68>:  mov    x0, x21
    0x190791528 <+72>:  bl     0x18f4db988               ; _NSIsNSDictionary
    0x19079152c <+76>:  tbz    w0, #0x0, 0x1907915c0     ; <+224>
    0x190791530 <+80>:  adrp   x8, 348516
    0x190791534 <+84>:  ldr    x0, [x8, #0x450]
    0x190791538 <+88>:  bl     0x18f54fbd0               ; symbol stub for: objc_alloc_init
    0x19079153c <+92>:  bl     0x18f54fbf0               ; symbol stub for: objc_autorelease
    0x190791540 <+96>:  mov    x22, x0
    0x190791544 <+100>: adrp   x8, 322262
    0x190791548 <+104>: add    x1, x8, #0xc72            ; =0xc72 
    0x19079154c <+108>: mov    x2, x21
    0x190791550 <+112>: mov    x3, x20
    0x190791554 <+116>: bl     0x18f0f4830
    0x190791558 <+120>: mov    x20, x0
    0x19079155c <+124>: cbz    x19, 0x190791578          ; <+152>
    0x190791560 <+128>: cbnz   x20, 0x190791578          ; <+152>
    0x190791564 <+132>: adrp   x8, 321279
    0x190791568 <+136>: add    x1, x8, #0x4b4            ; =0x4b4 
    0x19079156c <+140>: mov    x0, x22
    0x190791570 <+144>: bl     0x18f0f4830
    0x190791574 <+148>: str    x0, [x19]
    0x190791578 <+152>: mov    x0, x20
    0x19079157c <+156>: ldp    x29, x30, [sp, #0x40]
    0x190791580 <+160>: ldp    x20, x19, [sp, #0x30]
    0x190791584 <+164>: ldp    x22, x21, [sp, #0x20]
    0x190791588 <+168>: ldp    x24, x23, [sp, #0x10]
    0x19079158c <+172>: add    sp, sp, #0x50             ; =0x50 
    0x190791590 <+176>: retab  

    0x190791594 <+180>: adrp   x8, 348515
    0x190791598 <+184>: ldr    x19, [x8, #0x4c0]
    0x19079159c <+188>: mov    x0, x23
    0x1907915a0 <+192>: mov    x1, x22
    0x1907915a4 <+196>: bl     0x19086ec68               ; _NSMethodExceptionProem
    0x1907915a8 <+200>: adrp   x8, 348507
    0x1907915ac <+204>: ldr    x1, [x8, #0xbd0]
    0x1907915b0 <+208>: str    x0, [sp]
    0x1907915b4 <+212>: adrp   x2, 402695
    0x1907915b8 <+216>: add    x2, x2, #0xbe0            ; =0xbe0 
    0x1907915bc <+220>: b      0x1907915e8               ; <+264>

    0x1907915c0 <+224>: adrp   x8, 348515
    0x1907915c4 <+228>: ldr    x19, [x8, #0x4c0]
    0x1907915c8 <+232>: mov    x0, x23
    0x1907915cc <+236>: mov    x1, x22
    0x1907915d0 <+240>: bl     0x19086ec68               ; _NSMethodExceptionProem
    0x1907915d4 <+244>: adrp   x8, 348507
    0x1907915d8 <+248>: ldr    x1, [x8, #0xbd0]
    0x1907915dc <+252>: str    x0, [sp]
    0x1907915e0 <+256>: adrp   x2, 402695
    0x1907915e4 <+260>: add    x2, x2, #0xc00            ; =0xc00 

    0x1907915e8 <+264>: mov    x0, x19
    0x1907915ec <+268>: bl     0x18f0f4830
    0x1907915f0 <+272>: mov    x3, x0
    0x1907915f4 <+276>: adrp   x8, 348515
    0x1907915f8 <+280>: ldr    x0, [x8, #0x510]
    0x1907915fc <+284>: adrp   x8, 348478
    0x190791600 <+288>: ldr    x8, [x8, #0xd08]
    0x190791604 <+292>: ldr    x2, [x8]
    0x190791608 <+296>: adrp   x8, 319561
    0x19079160c <+300>: add    x1, x8, #0x57c            ; =0x57c 
    0x190791610 <+304>: mov    x4, #0x0
    0x190791614 <+308>: bl     0x18f0f4830
    0x190791618 <+312>: bl     0x18f54fcb0               ; symbol stub for: objc_exception_throw

The instruction at +312 is what actually throws the exception. If you look through the code you see there are two ways into this:

  • At +36 there’s a test for nil which branches to +180. At +220 this branches to +264, which gets you to the code that throws the exception.

  • At +76 there’s a type test which branches to +224. This falls through to the exception throwing code at +264.

In each case the message is set up at the end of the block. Consider this:

    0x1907915b4 <+212>: adrp   x2, 402695
    0x1907915b8 <+216>: add    x2, x2, #0xbe0            ; =0xbe0 

This is a key structure in 64-bit Arm code in that it calculates a PC-relative address. The adrp instruction takes the PC, masks off the bottom 12 bits, then adds in the argument shifted left by 12 bits. And the add instruction just adds the immediate value to its input. Together these form an expression like this:

(lldb) p/a (0x1907915b4 & ~0x0fff) + (402695 << 12) + 0xbe0
(long) $8 = 0x00000001f2c98be0 @"%@: value parameter is nil"

LLDB is smart enough to work out that this is an NSString pointer and print the literal. Yay!

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you very much for taking the time to write such a detailed explanation. It is much appreciated!

Here's the code where the crash is happening:

    NSMutableDictionary *body = [NSMutableDictionary new];

    [body setObject:Utils.deviceDict forKey:@"device"];

    NSMutableArray *trackingQueueCopy = trackingQueue.copy;
    NSMutableArray *locationObjects = [NSMutableArray new];

    for(LocationObject *locationObject in trackingQueueCopy) {
        NSDictionary *locationDict = @{@"latitude": @(locationObject.location.coordinate.latitude),
                                       @"longitude": @(locationObject.location.coordinate.longitude),
                                       @"locationTimestamp": @(round(locationObject.location.timestamp.timeIntervalSince1970)),
                                       @"altitude": @(locationObject.location.altitude),
                                       @"horizontalAccuracy": @(locationObject.location.horizontalAccuracy),
                                       @"verticalAccuracy": @(locationObject.location.verticalAccuracy),
                                       @"clientTimestamp": @(round(locationObject.dataTimestamp.timeIntervalSince1970)),
                                       @"battery": @(round(locationObject.batteryLevel*100)/100),
                                       @"speed": @(locationObject.location.speed),
                                       @"course": @(locationObject.location.course)
                                       };
        [locationObjects addObject:locationDict];
    }
    [body setObject:locationObjects forKey:@"locations"];

    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:body options:NSJSONWritingPrettyPrinted error:&error];

The crash occurs in fact on the very last line. But as can be seen on the first line of that snipped, body cannot be nil (at least to my understanding).

My guess is that somehow the content of body cannot be converted into JSON which is causing the crash but I have not been able to figure how how that can happen.

Unfortunately I don't have a specific user who can send a sysdiagnose so I would like to go by your suggestion of reproducing the crash and comparing the offset to the crash reports. But the thing is, I still have no clue where it could happen and therefore how to reproduce.

By looking a this snippet of code, do you see anything suspicious?

The crash occurs in fact on the very last line. But as can be seen on the first line of that snipped, body cannot be nil

Right. Well, it’s possible for +new to fail and return nil, but that can only happen if malloc fails, meaning that you’ve exhausted your address space. That’s unlikely to result in a consistent crash like this.

Unfortunately I don't have a specific user who can send a sysdiagnose so I would like to go by your suggestion of reproducing the crash and comparing the offset to the crash reports.

Understood. Can you post a full crash report here?

See Posting a Crash Report for advice on how to do that.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Right. Well, it’s possible for +new to fail and return nil, but that can only happen if malloc fails, meaning that you’ve exhausted your address space. That’s unlikely to result in a consistent crash like this.

Ok, I agree. But I also agree that it's very unlikely.

The crash report is attached. Thank you very much for your support!

The crash report is attached.

Oh, that’s very enlightening. Consider the backtrace of the crashing thread:

0   CoreFoundation  … __exceptionPreprocess + 220 …
1   libobjc.A.dylib … objc_exception_throw + 60 …
2   Foundation      … _writeJSONNumber + 1244 …
3   Foundation      … ___writeJSONObject_block_invoke + 388 …
4   CoreFoundation  … __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 24 …
5   CoreFoundation  … -[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:] + 184 …
6   Foundation      … _writeJSONObject + 508 …
7   Foundation      … ___writeJSONArray_block_invoke + 260 …
8   CoreFoundation  … __NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__ + 24 …
9   CoreFoundation  … -[__NSArrayM enumerateObjectsWithOptions:usingBlock:] + 196 …
10  Foundation      … _writeJSONArray + 340 …
11  Foundation      … ___writeJSONObject_block_invoke + 388 …
12  CoreFoundation  … __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 24 …
13  CoreFoundation  … -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 208 …
14  Foundation      … _writeJSONObject + 508 …
15  Foundation      … -[_NSJSONWriter dataWithRootObject:options:] + 88 …
16  Foundation      … +[NSJSONSerialization dataWithJSONObject:options:error:] + 120 …
17  Rrrr            … -[LocationManager sendPendingLocationObjects:] + 1988 

The exception isn’t coming directly from +dataWithJSONObject:options:error: but from something much deeper, namely _writeJSONNumber. Also, the pattern in the backtrace gives you a rough idea of the nesting, namely dictionary > array > dictionary > number. Finally, disassembling _writeJSONNumber (see below) reveals three potential causes:

  • Invalid number value (NaN) in JSON write
  • Invalid number value (infinite) in JSON write
  • Invalid number type in JSON write (%c)

I suspect it’s one of the first two.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

(lldb) disas -n _writeJSONNumber
Foundation`_writeJSONNumber:
    0x1826fa5bc <+0>:    pacibsp 
…
    0x1826fa9e0 <+1060>: brk    #0x1

    0x1826fa9e4 <+1064>: adrp   x8, 348530
    0x1826fa9e8 <+1068>: ldr    x0, [x8, #0x670]
    0x1826fa9ec <+1072>: adrp   x8, 358092
    0x1826fa9f0 <+1076>: ldr    x8, [x8, #0xe70]
    0x1826fa9f4 <+1080>: ldr    x2, [x8]
    0x1826fa9f8 <+1084>: adrp   x8, 297027
    0x1826fa9fc <+1088>: add    x1, x8, #0x8d1            ; =0x8d1 
    0x1826faa00 <+1092>: adrp   x3, 364346
    0x1826faa04 <+1096>: add    x3, x3, #0x768            ; =0x768 

(lldb) po 0x1826fa000+(364346<<12)+0x768
Invalid number value (NaN) in JSON write

    0x1826faa08 <+1100>: b      0x1826faa8c               ; <+1232>

    0x1826faa0c <+1104>: adrp   x8, 348530
    0x1826faa10 <+1108>: ldr    x0, [x8, #0x670]
    0x1826faa14 <+1112>: adrp   x8, 358092
    0x1826faa18 <+1116>: ldr    x8, [x8, #0xe70]
    0x1826faa1c <+1120>: ldr    x2, [x8]
    0x1826faa20 <+1124>: adrp   x8, 297027
    0x1826faa24 <+1128>: add    x1, x8, #0x8d1            ; =0x8d1 
    0x1826faa28 <+1132>: adrp   x3, 364346
    0x1826faa2c <+1136>: add    x3, x3, #0x788            ; =0x788 

(lldb) po 0x1826fa000+(364346<<12)+0x788
Invalid number value (infinite) in JSON write

    0x1826faa30 <+1140>: b      0x1826faa8c               ; <+1232>

    0x1826faa34 <+1144>: adrp   x8, 348530
    0x1826faa38 <+1148>: ldr    x19, [x8, #0x620]
    0x1826faa3c <+1152>: mov    x0, x20
    0x1826faa40 <+1156>: mov    x1, x22
    0x1826faa44 <+1160>: bl     0x180bc7e58     QQQ objc_msgSend
    0x1826faa48 <+1164>: ldrsb  x8, [x0]
    0x1826faa4c <+1168>: adrp   x9, 348522
    0x1826faa50 <+1172>: ldr    x1, [x9, #0x980]

(lldb) p *(char **)(0x1826fa000+(348522<<12)+0x980)
(char *) $5 = 0x00000001caf3a2e2 "stringWithFormat:"

    0x1826faa54 <+1176>: str    x8, [sp, #-0x10]!
    0x1826faa58 <+1180>: adrp   x2, 364346
    0x1826faa5c <+1184>: add    x2, x2, #0x7a8            ; =0x7a8 

(lldb) po 0x1826fa000+(364346<<12)+0x7a8
Invalid number type in JSON write (%c)

    0x1826faa60 <+1188>: mov    x0, x19
    0x1826faa64 <+1192>: bl     0x180bc7e58     QQQ objc_msgSend
    0x1826faa68 <+1196>: add    sp, sp, #0x10             ; =0x10 

    0x1826faa6c <+1200>: mov    x3, x0
    0x1826faa70 <+1204>: adrp   x8, 348530
    0x1826faa74 <+1208>: ldr    x0, [x8, #0x670]
    0x1826faa78 <+1212>: adrp   x8, 358092
    0x1826faa7c <+1216>: ldr    x8, [x8, #0xe70]
    0x1826faa80 <+1220>: ldr    x2, [x8]
    0x1826faa84 <+1224>: adrp   x8, 297027
    0x1826faa88 <+1228>: add    x1, x8, #0x8d1            ; =0x8d1 

(lldb) p (char *)(0x1826fa000+(297027<<12)+0x8d1)
(char *) $3 = 0x00000001caf3d8d1 "exceptionWithName:reason:userInfo:"

    0x1826faa8c <+1232>: mov    x4, #0x0
    0x1826faa90 <+1236>: bl     0x180bc7e58

(lldb) disas -c 3 -s 0x180bc7e58
    0x180bc7e58: adrp   x16, 100442
    0x180bc7e5c: add    x16, x16, #0x5c0          ; =0x5c0 
    0x180bc7e60: br     x16
(lldb) p/a 0x180bc7000+(100442<<12)+0x5c0
(long) $0 = 0x00000001994215c0 libobjc.A.dylib`objc_msgSend

    0x1826faa94 <+1240>: bl     0x181031af4               ; symbol stub for: objc_exception_throw

Thank you very much for the explanation, it totally makes sense.

Also, the pattern in the backtrace gives you a rough idea of the nesting, namely dictionary > array > dictionary > number.

Yes, that does indeed exactly match the structure as seen in the code snippet above.

Now in order to reproduct the issue, I wonder how I can generate a NaN or infinite value? The potential candidates in the locationDictfrom above are CLLocationDegrees, CLLocationDistance, CLLocationAccuracy, CLLocationSpeed, CLLocationDirection, NSTimeInterval, float. So basically we have double or float.

Ok, I've figured out that I can use something like @"latitude": @(nan("0x12345")) to replace @"latitude": @(locationObject.location.coordinate.latitude)in order to reproduce the exception.

The modified code from above is now:

    NSMutableDictionary *body = [NSMutableDictionary new];

    [body setObject:Utils.deviceDict forKey:@"device"];

    NSMutableArray *trackingQueueCopy = trackingQueue.copy;
    NSMutableArray *locationObjects = [NSMutableArray new];

    NSError *error;
    for(LocationObject *locationObject in trackingQueueCopy) {
        NSDictionary *locationDict = @{@"latitude": @(locationObject.location.coordinate.latitude),
                                       @"longitude": @(locationObject.location.coordinate.longitude),
                                       @"locationTimestamp": @(round(locationObject.location.timestamp.timeIntervalSince1970)),
                                       @"altitude": @(locationObject.location.altitude),
                                       @"horizontalAccuracy": @(locationObject.location.horizontalAccuracy),
                                       @"verticalAccuracy": @(locationObject.location.verticalAccuracy),
                                       @"clientTimestamp": @(round(locationObject.dataTimestamp.timeIntervalSince1970)),
                                       @"battery": @(round(locationObject.batteryLevel*100)/100),
                                       @"speed": @(locationObject.location.speed),
                                       @"course": @(locationObject.location.course)
                                       };
        @try {
            [NSJSONSerialization dataWithJSONObject:locationDict options:NSJSONWritingPrettyPrinted error:&error];
            [locationObjects addObject:locationDict];
        } @catch(NSException *exception) {
            DLog(@"invalid JSON payload, ignoring (%@)", exception);
        }
    }

That's sufficient enough for me to have this bug fixed.

Figuring out what exactly is actually causing the NaN would be a different story though, and I'm currently not sure how I could approach that...

That's sufficient enough for me to have this bug fixed.

Fixed? Not really, rather just “worked around”.

IMPORTANT Objective-C exceptions are intended for serious problems only and it’s not safe to catch them and continue running (assuming that anything in the call chain uses ARC).

In this situation it’s clearly better than crashing, but you’ll still need to fix this bug properly.

I've figured out that I can use something like

I’m presume that you chose the latitude property just as an example? Because I don’t see any info that points to that property in the crash report. It could be any of those floating point properties.

I'm currently not sure how I could approach that

Do you have a way to export that DLog to your analytics system? If so, that should be sufficient to tell you which property is causing problems.

If not, you should figure out a way to make that happen. One option I like is to add more analytics to the version of the app that you ship to beta tests via TestFlight. For example, your TestFlight build could actually display an alert when it sees this problem and offer a way for the user to send a report to you.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Fixed? Not really, rather just “worked around”.

Ok, I agree.

I’m presume that you chose the latitude property just as an example? 

Exactly.

Do you have a way to export that DLog to your analytics system? If so, that should be sufficient to tell you which property is causing problems.

Yes, I will add some mechanism to have that DLog output sent to me by the users. But that output is not really telling me which property was causing the issue, is it?

Its output is just: invalid JSON payload, ignoring (Invalid number value (NaN) in JSON write)

Am I missing something?

But that output is not really telling me which property was causing the issue, is it?

Ah, good point. You’d have to add code that logs the entire dictionary, or hunts for the bogus value in the dictionary. And if you do the latter you might as well do it when you build the dictionary, that is, add checks for NaN and infinity for each property you serialise in your for loop.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"