rollbar / rollbar-ios

Objective-C library for crash reporting and logging with Rollbar.

Home Page:https://docs.rollbar.com/docs/ios

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Infinite loop in [NSJSONSerialization(Rollbar) safeDataFromJSONObject:]

okalentiev opened this issue · comments

Looking for assistance figuring out how to fix this crash. It happens on production so there are no exact steps on how to reproduce it, unfortunately.

Rollbar iOS Version: 1.12.14
iOS: 13.6.1 (17G80)
Device: iPhone 8 Plus

Crashed: Thread #1
0  libswiftCore.dylib             0x18dc74d04 swift_unknownObjectRetain + 16
1  libswiftCore.dylib             0x18dbe7fcc specialized _SwiftDeferredNSDictionary.enumerateKeysAndObjects(options:using:) + 584
2  libswiftCore.dylib             0x18d9df364 @objc _SwiftDeferredNSDictionary.enumerateKeysAndObjects(options:using:) + 44
3  Rollbar                        0x1058f6798 +[NSJSONSerialization(Rollbar) safeDataFromJSONObject:] + 39 (NSJSONSerialization+Rollbar.m:39)
4  Rollbar                        0x1058f683c __55+[NSJSONSerialization(Rollbar) safeDataFromJSONObject:]_block_invoke + 41 (NSJSONSerialization+Rollbar.m:41)
5  CoreFoundation                 0x18050ec14 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 16
6  CoreFoundation                 0x18039951c -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 204
7  Rollbar                        0x1058f6798 +[NSJSONSerialization(Rollbar) safeDataFromJSONObject:] + 39 (NSJSONSerialization+Rollbar.m:39)
8  Rollbar                        0x1058f683c __55+[NSJSONSerialization(Rollbar) safeDataFromJSONObject:]_block_invoke + 41 (NSJSONSerialization+Rollbar.m:41)
9  CoreFoundation                 0x18050ec14 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 16
10 CoreFoundation                 0x18039951c -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 204
11 Rollbar                        0x1058f6798 +[NSJSONSerialization(Rollbar) safeDataFromJSONObject:] + 39 (NSJSONSerialization+Rollbar.m:39)
12 Rollbar                        0x1058f683c __55+[NSJSONSerialization(Rollbar) safeDataFromJSONObject:]_block_invoke + 41 (NSJSONSerialization+Rollbar.m:41)
13 CoreFoundation                 0x18050ec14 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 16
14 CoreFoundation                 0x18039951c -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 204
15 Rollbar                        0x1058f6798 +[NSJSONSerialization(Rollbar) safeDataFromJSONObject:] + 39 (NSJSONSerialization+Rollbar.m:39)
16 Rollbar                        0x1058f683c __55+[NSJSONSerialization(Rollbar) safeDataFromJSONObject:]_block_invoke + 41 (NSJSONSerialization+Rollbar.m:41)
17 CoreFoundation                 0x18050ec14 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 16
18 CoreFoundation                 0x18039951c -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 204
19 Rollbar                        0x1058f6798 +[NSJSONSerialization(Rollbar) safeDataFromJSONObject:] + 39 (NSJSONSerialization+Rollbar.m:39)
20 Rollbar                        0x1058f683c __55+[NSJSONSerialization(Rollbar) safeDataFromJSONObject:]_block_invoke + 41 (NSJSONSerialization+Rollbar.m:41)
21 CoreFoundation                 0x18050ec14 __NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 16
22 CoreFoundation                 0x18039951c -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 204
23 Rollbar                        0x1058f6798 +[NSJSONSerialization(Rollbar) safeDataFromJSONObject:] + 39 (NSJSONSerialization+Rollbar.m:39)
24 Rollbar                        0x1058f6664 +[NSJSONSerialization(Rollbar) dataWithJSONObject:options:error:safe:] + 29 (NSJSONSerialization+Rollbar.m:29)
25 Rollbar                        0x105905960 -[RollbarNotifier queuePayload_OnlyCallOnThread:] + 645 (RollbarNotifier.m:645)
26 Foundation                     0x1808b9f78 __NSThreadPerformPerform + 184
27 CoreFoundation                 0x18043fad8 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
28 CoreFoundation                 0x18043fa30 __CFRunLoopDoSource0 + 80
29 CoreFoundation                 0x18043f1b8 __CFRunLoopDoSources0 + 184
30 CoreFoundation                 0x18043a1e8 __CFRunLoopRun + 788
31 CoreFoundation                 0x180439ba8 CFRunLoopRunSpecific + 424
32 Foundation                     0x18078b01c -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 228
33 Rollbar                        0x10590ebe0 -[RollbarThread run] + 65 (RollbarThread.m:65)
34 Foundation                     0x1808b9e20 __NSThread__start__ + 848
35 libsystem_pthread.dylib        0x1801d7d98 _pthread_start + 156
36 libsystem_pthread.dylib        0x1801db74c thread_start + 8

@okalentiev , are you adding any custom data to the payloads when reporting to Rollbar? Could it be you have circular references in the custom data?
In general, we do make recursive calls when serializing the payloads to JSON. So, it is not unusual to see repetitive calls in the serialization stack. And we definitely not watching for circular data references within the serializable objects at the moment. The explicitly defined payload objects do not have any such references but any attached custom data potentially can have it and it would definitely resolve in an infinite loop during the serialization. Please, make sure not to add complex objects as custom data to the payloads.

Yes @akornich, judging from our previous crash investigations this is most definitely the case 🙂

We are already doing extensive filtering for the custom objects, but it might be that there are circular references. Though I would expect that we catch them before sending them to Rollbar 🤔

extension Dictionary where Key == AnyHashable, Value == Any {
    // - Turn URLs to strings for rollbar tracking and return if new data is valid json
    // - Map nil to NSNull() for JSONSerialization
    var rollbarFiltered: [AnyHashable: Any]? {
        var cleanData = [AnyHashable: Any]()
        for (key, value) in self {
            switch value {
            case is URL:
                cleanData[key] = (value as! URL).absoluteString
            case Optional<Any>.none:
                cleanData[key] = NSNull()
            case is [AnyHashable: Any]:
                cleanData[key] = (value as? [AnyHashable: Any])?.rollbarFiltered ?? NSNull()
            default:
                cleanData[key] = value
            }
        }
        if JSONSerialization.isValidContent(cleanData) {
            return cleanData
        }
        assertionFailure("Tried to send invalid json to rollbar: \(cleanData)")
        return nil
    }
}

extension JSONSerialization {
    static func isValidContent(_ content: Any) -> Bool {
        if content is NSString || content is NSNumber {
            return true
        } else if content is NSArray || content is NSDictionary {
            return JSONSerialization.isValidJSONObject(content)
        } else {
            return false
        }
    }
}

I'm trying to think of a fast solution for now before we review our logging properly. We can do an extra step to map out the dictionary to string, then we might be able to avoid circular references. What do you think?

@okalentiev, I see what you mean.
On a few occasions when I was implementing very complex application-specific models that were expected to support very detailed logging/tracing capability, I went with the concept of Traceable interface defining mainly one method TraceAsString that would produce a well-formatted string (multi-line string, in general) representation of the object claiming to implement Traceable interface. I had a basic reusable implementation of the method based on type-reflection of only properties of primitive types as well as Traceable ones (and collections of those). That gave me a great starting point with only a hand full of instances when I had to override it with a custom more elaborate implementation.
Then my logging infrastructure would first try to resolve any incoming object to include into a log as Traceable and call TraceAsString on it in order to inject the object trace into a log record.
You can go with a similar approach when turning your objects of interest into the Rollbar custom data values.

BTW, regarding your snippet, we have a somewhat similar solution implemented here.

Thanks for the suggestions! I think we are far from that at this point, but it's definitely something we should consider. I believe it might be even more straightforward to utilize Swift's CustomDebugStringConvertible protocol. Our issue is probably coming from some JSON responses that we are logging for debugging purposes and since we pass them as dictionaries it causes issues. I will flatten them to strings so as not to confuse mapping logic.

I actually didn't realize that Rollbar sunset's this version. I will make sure we switch to the Rollbar-Apple asap.

@okalentiev , you are welcome! yes, we are moving from Rollbar-iOS to Rollbar-Apple. Rollbar-Apple is lightweight compare to Rollbar-iOS. It is modular. Crash reporting is optional and we are planning to support a few crash reporting alternatives. KSCrash and PLCrashReporter are already supported. Rollbar-Apple SDK is natively built on top of SwiftPM and provides a cleaner API and more Swift friendlier.
@okalentiev , can we close this issue or you'll need further support on this?

Sure, thanks for the follow-up.