10

Crashlytics reported this crash in one of my apps and I am not able to reproduce it at all, no matter what I do. This happens to about 5% of the users, so it's a pretty big deal. I'm posting screenshots with the crash report and also the methods that are mentioned in the crash report. Any idea how to solve this?

Crash Report

This is where the app crashed:

#pragma mark - custom transformations
-(BOOL)__customSetValue:(id<NSObject>)value forProperty:(JSONModelClassProperty*)property
{
    if (!property.customSetters)
        property.customSetters = [NSMutableDictionary new];

    NSString *className = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:[value class]]);

    if (!property.customSetters[className]) {
        //check for a custom property setter method
        NSString* ucfirstName = [property.name stringByReplacingCharactersInRange:NSMakeRange(0,1)
                                                                       withString:[[property.name substringToIndex:1] uppercaseString]];
        NSString* selectorName = [NSString stringWithFormat:@"set%@With%@:", ucfirstName, className];

        SEL customPropertySetter = NSSelectorFromString(selectorName);

        //check if there's a custom selector like this
        if (![self respondsToSelector: customPropertySetter]) {
            property.customSetters[className] = [NSNull null]; // this is line 855
            return NO;
        }

        //cache the custom setter selector
        property.customSetters[className] = selectorName;
    }

    if (property.customSetters[className] != [NSNull null]) {
        //call the custom setter
        //https://github.com/steipete
        SEL selector = NSSelectorFromString(property.customSetters[className]);
        ((void (*) (id, SEL, id))objc_msgSend)(self, selector, value);
        return YES;
    }

    return NO;
}

This is the originating method:

-(void)reloadUserInfoWithCompletion:(void (^) (LoginObject *response))handler andFailure:(void (^)(NSError *err))failureHandler {
    NSString *lat;
    NSString *lon;

    lat = [NSString stringWithFormat:@"%.6f",[[LocationManager sharedInstance] getPosition].coordinate.latitude];
    lon = [NSString stringWithFormat:@"%.6f",[[LocationManager sharedInstance] getPosition].coordinate.longitude];

    NSMutableDictionary *params = [NSMutableDictionary new];
    [params setObject:lat forKey:@"latitude"];
    [params setObject:lon forKey:@"longitude"];

    [[LoginHandler sharedInstance] getLoginToken:^(NSString *response) {

        NSDictionary *headers;
        if (response) {
            headers = @{@"Login-Token":response};
        }
        GETRequest *req = [GETRequest new];
        [req setCompletionHandler:^(NSString *response) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                NSLog(@"response: %@",response);
                NSError *err = nil;
                self.loginObject.userDetails = [[User alloc] initWithString:response error:&err]; // <- this is the line reported in the crash
                [self storeLoginObject];
                NSLog(@"%@",self.loginObject.userDetails);
//                [Utils updateFiltersFullAccessIfAll]; 
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (handler) {
                        handler(self.loginObject);
                    }
                });
            });
        }];
        [req setFailedHandler:^(NSError *err) {
            if (failureHandler) {
                failureHandler(err);
            }
        }];
        NSLog(@"%@",params);
        [req requestWithLinkString:USER_DETAILS parameters:nil andHeaders:headers];
    }];

}
Eric Galluzzo
  • 3,191
  • 1
  • 20
  • 20
stonycis
  • 466
  • 4
  • 16
  • 1
    Dont post screen shots of your code, copy/paste it. It's simpler for us, if we want to copy your code to show the error, we have to write it manually (no copy/paste), and it's also harder to read... – Larme Jan 26 '16 at 13:03
  • I posted the screenshots to outline the line numbers, but I'll edit the question. – stonycis Jan 26 '16 at 13:04

2 Answers2

2

So setObject:forKey: can cause problems in two ways. 1. If object is nil or 2. the key is nil. Both could cause the crash you are seeing. Given that you are setting the object to [NSNull null] it is probably safe to assume that it is the key giving you problems (on line 855).

Walking back from there that would reveal that className is nil. If you look, your code does not protect against this. You make an assumption here that NSStringFromClass (a couple lines before) is giving you back a valid string, which assumes that the value originally passed into the method is non-nil. If it is nil it would make it past all of your checks, including !property.customSetters[className], since this would be !nil allowing it to enter the if.

If I am reading your code right (a bit hard since I cannot test any of my assumptions) NSLog(@"response: %@",response); would print out a nil response.

Try seeing how your code handles these unexpected nils and let me know in the comments how things go.

Firo
  • 15,448
  • 3
  • 54
  • 74
  • If the "response" object would be nil, JSONModel should be able to handle it. Look at the methods from the stack trace in JSONModel library ([here](https://github.com/icanzilb/JSONModel/blob/master/JSONModel/JSONModel/JSONModel.m)). I can't paste the code in the comments (too long) – stonycis Jan 30 '16 at 19:54
  • even if I manually set "response" to nil, it doesn't even touch that piece of code that crashes. – stonycis Jan 30 '16 at 20:44
  • @stonycis Thanks for your response. I'll take a look at it again (no time right now) and update my answer. – Firo Jan 30 '16 at 20:46
  • Can you check one thing for me. If `className` (at the beginning of the first method) is `nil` do you get a similar crash? – Firo Jan 30 '16 at 20:55
  • 1
    yeah, if I force `className` to be `nil`, that crash will occur (it was one of the first things I tested), but I can't create a scenario to reproduce it without manually setting it to `nil`. – stonycis Jan 30 '16 at 20:58
  • if you go step by step through the methods inside JSONModel (the ones from the crash report) you'll see that `className` should never be `nil` (or am I missing something?)... – stonycis Jan 30 '16 at 21:03
  • here's an idea... is there a chance that multiple concurrent calls (from different threads) to `reloadUserInfoWithCompletion` method could produce that crash? (still not able to reproduce it) – stonycis Feb 01 '16 at 09:10
0

If you don't use model custom setters you can replace JSONModel __customSetValue:forProperty: with swizzling or Aspects library

#import "JSONModel+Aspects.h"
#import "JSONModel.h"
#import "Aspects.h"

@implementation JSONModel (Aspects)

+(void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [JSONModel aspect_hookSelector:@selector(__customSetValue:forProperty:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> aspectInfo) {
            return NO;
        } error:NULL];
    });
}

@end
Vadim Kurochkin
  • 426
  • 1
  • 4
  • 6