highcharts-ios icon indicating copy to clipboard operation
highcharts-ios copied to clipboard

Crash: Invalid number value (NaN) in JSON write

Open skuske opened this issue 5 years ago • 20 comments

We are seeing this issue quite a few times on user devices, but cannot reproduce the issue on our devices. It looks like the wrapper prepares series data without checking if one value might be NSNull, NaN, or nil. Might be the same issue or related to https://github.com/highcharts/highcharts-ios/issues/91

Fatal Exception: NSInvalidArgumentException
0  CoreFoundation                 0x1ab03dc30 __exceptionPreprocess
1  libobjc.A.dylib                0x1aad580c8 objc_exception_throw
2  Foundation                     0x1ab4b8a68 _writeJSONNumber
3  Foundation                     0x1ab4b97e0 ___writeJSONObject_block_invoke
4  Foundation                     0x1ab4b99b0 ___writeJSONObject_block_invoke_2
5  CoreFoundation                 0x1ab08db20 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__
6  CoreFoundation                 0x1aaf0dd50 -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]
7  Foundation                     0x1ab4b8cd0 _writeJSONObject
8  Foundation                     0x1ab4b9b00 ___writeJSONArray_block_invoke
9  CoreFoundation                 0x1ab00f7c4 __NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__
10 CoreFoundation                 0x1aaf0d424 -[__NSArrayM enumerateObjectsWithOptions:usingBlock:]
11 Foundation                     0x1ab4b919c _writeJSONArray
12 Foundation                     0x1ab4b97e0 ___writeJSONObject_block_invoke
13 Foundation                     0x1ab4b99b0 ___writeJSONObject_block_invoke_2
14 CoreFoundation                 0x1ab08db20 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__
15 CoreFoundation                 0x1aaf0dd50 -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]
16 Foundation                     0x1ab4b8cd0 _writeJSONObject
17 Foundation                     0x1ab4b9b00 ___writeJSONArray_block_invoke
18 CoreFoundation                 0x1ab00f7c4 __NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__
19 CoreFoundation                 0x1aaf0d424 -[__NSArrayM enumerateObjectsWithOptions:usingBlock:]
20 Foundation                     0x1ab4b919c _writeJSONArray
21 Foundation                     0x1ab4b97e0 ___writeJSONObject_block_invoke
22 Foundation                     0x1ab4b99b0 ___writeJSONObject_block_invoke_2
23 CoreFoundation                 0x1ab08db20 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__
24 CoreFoundation                 0x1aaf0f938 -[__NSFrozenDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]
25 Foundation                     0x1ab4b8cd0 _writeJSONObject
26 Foundation                     0x1ab361a34 -[_NSJSONWriter dataWithRootObject:options:error:]
27 Foundation                     0x1ab3616cc +[NSJSONSerialization dataWithJSONObject:options:error:]
28 Highcharts                     0x100a31a28 _hidden#4343_
29 Highcharts                     0x100a7bc38 _hidden#5709_
30 Highcharts                     0x100a013d4 _hidden#3459_
31 Highcharts                     0x100a01838 _hidden#3463_
32 Foundation                     0x1ab433ac4 __NSFireTimer
33 CoreFoundation                 0x1aafb98d4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
34 CoreFoundation                 0x1aafb960c __CFRunLoopDoTimer
35 CoreFoundation                 0x1aafb8c80 __CFRunLoopDoTimers
36 CoreFoundation                 0x1aafb3b28 __CFRunLoopRun
37 CoreFoundation                 0x1aafb3098 CFRunLoopRunSpecific
38 GraphicsServices               0x1b511d534 GSEventRunModal
39 UIKitCore                      0x1af0d37ac UIApplicationMain
crash

skuske avatar Oct 17 '19 08:10 skuske

Hello @skuske,

thank you for the reporting.

It's hard to say something without reproducing the issue. Do you think the problem is in series.data?

series.data may contain NSNull and nil values. Please see an example below:

let series = HISeries()
var c: Double?
series.data = [NSNull(), 71.5, 106.4, 129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, c]

So, there is only one thing to check is a NaN. Can your data contain the NaN value?

ihnatmoisieiev avatar Oct 18 '19 06:10 ihnatmoisieiev

Hello @ihnatmoisieiev

yes, the series data might contain NaN. We retrieve data from a remote server, and sometimes missing data is null, and I think it results in NaN if converted to NSNumber before checking if it is null.

We have now integrated a check for null values within the raw data and replace them with 0 before converting them to NSNumber. That probably fixes it, but it might be a good idea to make the Highcharts wrapper check any incoming data for NaN, too.

skuske avatar Oct 18 '19 07:10 skuske

@skuske the main Highcharts library also doesn't have that functionality. So, you should check it by yourself while preparing the data.

ihnatmoisieiev avatar Oct 21 '19 06:10 ihnatmoisieiev

@ihnatmoisieiev

Yes, we are doing that now. Thanks! :o)

skuske avatar Oct 21 '19 07:10 skuske

@ihnatmoisieiev

I am reopening this issue as we need additional information on how to get this fixed.

Although we have implemented a lot of checks, we still see those NaN errors - actually we do check every data array for NSNull or NaN values and replace or remove them from the array, but the problem is still there:

Fatal Exception: NSInvalidArgumentException
Invalid number value (NaN) in JSON write
_hidden#4343_
Fatal Exception: NSInvalidArgumentException
0  CoreFoundation                 0x1a6143ab0 __exceptionPreprocess
1  libobjc.A.dylib                0x1a5e5d028 objc_exception_throw
2  Foundation                     0x1a65bea28 _writeJSONNumber
3  Foundation                     0x1a65bf7a0 ___writeJSONObject_block_invoke
4  Foundation                     0x1a65bf970 ___writeJSONObject_block_invoke_2
5  CoreFoundation                 0x1a61939e0 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__
6  CoreFoundation                 0x1a6013ca0 -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]
7  Foundation                     0x1a65bec90 _writeJSONObject
8  Foundation                     0x1a65bfac0 ___writeJSONArray_block_invoke
9  CoreFoundation                 0x1a61157b0 __NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__
10 CoreFoundation                 0x1a6013374 -[__NSArrayM enumerateObjectsWithOptions:usingBlock:]
11 Foundation                     0x1a65bf15c _writeJSONArray
12 Foundation                     0x1a65bf7a0 ___writeJSONObject_block_invoke
13 Foundation                     0x1a65bf970 ___writeJSONObject_block_invoke_2
14 CoreFoundation                 0x1a61939e0 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__
15 CoreFoundation                 0x1a6013ca0 -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]
16 Foundation                     0x1a65bec90 _writeJSONObject
17 Foundation                     0x1a65bfac0 ___writeJSONArray_block_invoke
18 CoreFoundation                 0x1a61157b0 __NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__
19 CoreFoundation                 0x1a6013374 -[__NSArrayM enumerateObjectsWithOptions:usingBlock:]
20 Foundation                     0x1a65bf15c _writeJSONArray
21 Foundation                     0x1a65bf7a0 ___writeJSONObject_block_invoke
22 Foundation                     0x1a65bf970 ___writeJSONObject_block_invoke_2
23 CoreFoundation                 0x1a61939e0 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__
24 CoreFoundation                 0x1a6015888 -[__NSFrozenDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]
25 Foundation                     0x1a65bec90 _writeJSONObject
26 Foundation                     0x1a64677f4 -[_NSJSONWriter dataWithRootObject:options:error:]
27 Foundation                     0x1a646748c +[NSJSONSerialization dataWithJSONObject:options:error:]
28 Highcharts                     0x100b71a28 _hidden#4343_
29 Highcharts                     0x100bbbc38 _hidden#5709_
30 Highcharts                     0x100b413d4 _hidden#3459_
31 Highcharts                     0x100b41838 _hidden#3463_
32 Foundation                     0x1a6539acc __NSFireTimer
33 CoreFoundation                 0x1a60bf8c0 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
34 CoreFoundation                 0x1a60bf5f8 __CFRunLoopDoTimer
35 CoreFoundation                 0x1a60bec6c __CFRunLoopDoTimers
36 CoreFoundation                 0x1a60b9b14 __CFRunLoopRun
37 CoreFoundation                 0x1a60b9084 CFRunLoopRunSpecific
38 GraphicsServices               0x1b0307534 GSEventRunModal
39 UIKitCore                      0x1aa229670 UIApplicationMain
40 myapp                          0x1003ace70 main + 120 (main.m:120)
41 libdyld.dylib                  0x1a5f38e18 start

We doubt that this is actually coming from a series.data as we filtered out all NaN and al NSNull values before passing the data to Highcharts. However, since the stack trace does not give any hint, we need some additional information where exactly Highcharts crashes here.

What is actually happening in _hidden#4343. Can someone shed some light on this, please?

Our NaN check looks like this:

[numValue isEqualToNumber:[NSDecimalNumber notANumber]] (see here)

where numValue is a NSNumber.

Many thanks.

skuske avatar Dec 05 '19 07:12 skuske

@ihnatmoisieiev

In addition to my previous reply: further above you say

the main Highcharts library also doesn't have that functionality.

I checked the issues of the JS Highcharts library, and yes: it checks for null or nil, but not for NaN (https://github.com/highcharts/highcharts/issues/3571).

Why don't you make the iOS wrapper check for NaN? The crash occurs in writeJSONNumber and it would be so easy to just check if the value is NaN before writeJSONNumber is actually called. That would just fix it.

skuske avatar Dec 05 '19 07:12 skuske

@ihnatmoisieiev

We have now even implemented an

isnan

check on every (!!!) value of the data array, but the problem still exists. Our data is free from any NaN, NSNull, or nil value, but Highcharts still crashes. Not on our devices, but on our user's.

PLEASE (!) include an isnan check before writeJSONObject is called. I have no idea what we can do here to fix that issue as we have run out of options now. We have implemented so many checks, but the problem still exists.

And please reply. Many thanks.

skuske avatar Dec 08 '19 06:12 skuske

@skuske

thanks for paying attention.

As you can see, writeJSONNumber is a method from the Foundation framework and we can't do anything with it.

Please, prepare some example data to reproduce the issue, because it is impossible at the moment.

As I mentioned here https://github.com/highcharts/highcharts-ios/issues/248#issuecomment-544372527, the main HC framework doesn't have this check.

I also will discuss what we can do for this topic and let you know.

ihnatmoisieiev avatar Dec 08 '19 07:12 ihnatmoisieiev

@ihnatmoisieiev

But the Highcharts wrapper calls the writeJSONNumber method. And before calling it with a specific value, it should just check if the value is NaN or not... If it is NaN, it should replace it with 0 or just skip the value...

I cannot provide a sample as the issue never appeared on our devices, but lots of users do see the issue, and then it disappears again. Therefore a simple check for NaN by the wrapper would just be the logical consequence and the only possible fix for that.

skuske avatar Dec 08 '19 07:12 skuske

@ihnatmoisieiev

In addition to my previous reply:

we do check the series.data array for NaN and NSNull or nil values, and we do remove them all if they appear within the array. However, the issue is still there, and since we do not know where exactly the Highcharts wrapper calls writeJSONNumber and for what specific property of the chart it calls it, there's just nothing we can do against the issue.

If the wrapper calls writeJSONNumber just for the series data, then the issue would still be the data array. I think the wrapper calls it for all chart options all together and then it sends the data to the Highcharts core. And somewhere within the chart options there must be a NaN value causing writeJSONNumber to crash.

Even though Highcharts JS does not check for NaN values: what is the reason that the iOS wrapper does not do that at least?

Performance can not be the real reason, because what's the advantage of a slightly better (but hardly noticeable) performance when it crashes on the other hand, just because you skip a NaN check because of performance?

An empty chart (because of incomplete options) is still better than a crashing app. Don't you think so, too?

skuske avatar Dec 08 '19 09:12 skuske

Hi @skuske, We are actually working on the new v8 version which should be available before Christmas. Let wait until new release and then get back to tests.

sebastianbochan avatar Dec 09 '19 09:12 sebastianbochan

@sebastianbochan

That sounds somewhat promising ... :o)

skuske avatar Dec 09 '19 09:12 skuske

@skuske does the issue still appear for you? Can I close it?

ihnatmoisieiev avatar May 19 '20 18:05 ihnatmoisieiev

I am re-opening this issue because we see it happening again, although I am not sure if it's related to the latest release.

Anyway, I did some investigation and found out that dataWithJSONObject only works if

The top level object is an NSArray or NSDictionary, unless you set the NSJSONWritingFragmentsAllowed option. All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull. All dictionary keys are instances of NSString. Numbers are neither NaN or infinity.

To avoid a crash, you can pass the object to be converted to JSON to isValidJSONObject - see https://developer.apple.com/documentation/foundation/nsjsonserialization/1418461-isvalidjsonobject

I would suggest you include that check to the wrapper to avoid such crashes:

json-crash

skuske avatar Mar 21 '22 07:03 skuske