views:

258

answers:

2

Final Update: Turns out the fix to this problem is a depressing one. Go into the .xcodeproj directory inside your project, and remove the <username>.mode1v3 and <username>.pbxuser files. That fixed it. Boo, Xcode.


I'll start this off by saying that it's not your usual retain/release bug. It just started happening on a development branch of my code, the bug isn't present on the master branch, and I'm having trouble finding any differences in the source that point to the answer. So I'll just get to the gory stuff.

I've got a view controller class. It has a couple of properties on it referencing a couple of other view controllers. Each is loaded via getter accessor as needed, like so:

- (NotesViewController *)notesViewController {
    if (notesViewController == nil) {
        notesViewController = [[NotesViewController alloc] initWithNibName:@"NotesViewController" bundle:nil];
    }
    return notesViewController;
}

When you later on call [self notesViewController], it's all good. This one, however, just stopped working:

- (DateFieldController *)dateFieldController {
    NSLog(@"made it into the method at least ...");
    if (dateFieldController == nil) {
        NSLog(@"nib loader, don't let us down...");
        dateFieldController = [[DateFieldController alloc] initWithNibName:@"DateFieldController" bundle:nil];
    }
    return dateFieldController;
}

When this method gets called the same way as the other accessors, it bombs out, apparently during the nib load, but possibly before it:

2010-07-08 11:44:58.029 MyApp[24404:207] made it into the method at least ...
2010-07-08 11:44:58.030 MyApp[24404:207] nib loader, don't let us down...
Program received signal:  “EXC_BAD_ACCESS”.
(gdb) bt
#0  0x028bb0ca in _class_isInitialized ()
#1  0x028b9eca in _class_initialize ()
#2  0x028bf1f6 in prepareForMethodLookup ()
#3  0x028b86c9 in lookUpMethod ()
#4  0x028b8836 in _class_lookupMethodAndLoadCache ()
#5  0x028c6ad3 in objc_msgSend ()
#6  0x00007bb3 in -[EntryViewController controllerForFieldType:] (self=0x7942d70, _cmd=0x190c20, type=0x79844b0) at /Users/wgray/Documents/Sources/iPhone/MyApp-iphone/Classes/EntryViewController.m:481
#7  0x00007e07 in -[EntryViewController selectTypesControllerDidSelect:] (self=0x7942d70, _cmd=0x190bb4, type=0x79844b0) at /Users/wgray/Documents/Sources/iPhone/MyApp-iphone/Classes/EntryViewController.m:527
#8  0x0000d2ad in -[TypesViewController tableView:didSelectRowAtIndexPath:] (self=0x5d2aac0, _cmd=0x1eac458, aTableView=0x6056000, indexPath=0x781c780) at /Users/wgray/Documents/Sources/iPhone/MyApp-iphone/Classes/TypesViewController.m:81
#9  0x0059e718 in -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] ()
#10 0x00594ffe in -[UITableView _userSelectRowAtIndexPath:] ()
#11 0x002abcea in __NSFireDelayedPerform ()
#12 0x0274cd43 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ ()
#13 0x0274e384 in __CFRunLoopDoTimer ()
#14 0x026aad09 in __CFRunLoopRun ()
#15 0x026aa280 in CFRunLoopRunSpecific ()
#16 0x026aa1a1 in CFRunLoopRunInMode ()
#17 0x02fd02c8 in GSEventRunModal ()
#18 0x02fd038d in GSEventRun ()
#19 0x0053ab58 in UIApplicationMain ()
#20 0x00001e24 in main (argc=1, argv=0xbffff050) at /Users/wgray/Documents/Sources/iPhone/MyApp-iphone/main.m:14
(gdb)

It would appear that something nasty is happening in the Simulator SDK when firing the nib load method. In fact, possibly way earlier. Over-riding the nib loading initializer with a little NSLog action in the DateFieldController gets us suspiciously nowhere:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    NSLog(@"come on, you...");
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) 
    {
    NSLog(@"rock on!");
  }
  return self;
}

Suffice to say, neither of these NSLog statements is ever executed.

So what's going on? Why is the runtime blarfing on me? (Switching the call from self.dateFieldController to [self dateFieldController] doesn't fix the bug -- and remember, this code is exactly the same as that on my master branch, where it isn't crashing).

FWIW, I'm building with XCode 3.2.3 64-bit, compiling for Simulator 4.0, Debug, i386 arch (on a new Mac Book Pro running OS X 10.6.3), Deployment Target in Project Settings is set to "iPhone OS 3.0" (uppping this to 3.1.3 doesn't fix it either).

Update: as requested, the implementation of controllerForFieldType::

- (id)controllerForFieldType:(Type *)type {
    id controller = nil;

    if ([type.mode isEqualToString:@"note"]) {
        controller = self.notesViewController;
    } else if ([type.mode isEqualToString:@"date"]) {
        NSLog(@"going for it, attempting to load dateFieldController from accessor...");
        controller = self.dateFieldController;
    } else {
        controller = self.fieldViewController;
    }

    return controller;
}

I left this out earlier because I don't think it's particularly relevant, the problem seems to be happening later in the stack, in the actual getter method dateFieldController.

Update II: At @zneak's suggesting, I turned on the setting Run -> Enable Guard Malloc and ran through the scenario again. The results are basically the same, but the internal backtrace in the runtime is different. It would appear that something's wrong with the Class or the NIB that's preventing it from being decoded:

2010-07-08 12:26:26.180 Strip[24961:207] made it into the method at least ...
2010-07-08 12:26:26.289 Strip[24961:207] nib loader, don't let us down...
Program received signal:  “EXC_BAD_ACCESS”.
Data Formatters temporarily unavailable, will re-try after a 'continue'. (Not safe to call dlopen at this time.)
(gdb) bt
#0  0x028cd41f in attachMethodLists ()
#1  0x028cdf77 in realizeClass ()
#2  0x028cedad in _class_getNonMetaClass ()
#3  0x028c8eb0 in _class_initialize ()
#4  0x028ce1f6 in prepareForMethodLookup ()
#5  0x028c76c9 in lookUpMethod ()
#6  0x028c7836 in _class_lookupMethodAndLoadCache ()
#7  0x028d5ad3 in objc_msgSend ()
#8  0x00007b81 in -[EntryViewController controllerForFieldType:] (self=0x38cfaf30, _cmd=0x190c8f, type=0x3cacbfe0) at /Users/wgray/Documents/Sources/iPhone/strip-iphone/Classes/EntryViewController.m:482
...snip...
#22 0x00001de4 in main (argc=1, argv=0xbfffefec) at /Users/wgray/Documents/Sources/iPhone/strip-iphone/main.m:14
(gdb)

Update III: In which the plot thickens. At @ohorob's suggestion in the comments, I added the env arg OBJC_PRINT_INITIALIZE_METHODS=YES and found that every single class initialization in the app looks good except the one in question here. Each looks about like this in the console output:

objc[26750]: INITIALIZE: calling +[UIPinchGestureRecognizer initialize]
objc[26750]: INITIALIZE: finished +[UIPinchGestureRecognizer initialize]
objc[26750]: INITIALIZE: UIPinchGestureRecognizer is fully +initialized

When I run the app and select the table row that leads to the explosion, we just see the nslog console output before the crash, and the crash itself. I'd like to think this means we could rule out improper initialization, but I'm not enough of an expert on the runtime.

Did a thorough audit of code differences between master branch and this dev branch, and found nothing indicating that I've stepped on a pointer just yet, but I keep thinking that's the mostly likely think that's going on.

A: 

Something is going squidgy during class initialization.

Try setting the OBJC_PRINT_INITIALIZE_METHODS environment variable to YES and running your application. That might give us a clue. Or not. (Set the environment variable in Xcode's executable inspector -- see the docs).

Do you have any +initialize methods? (in any class -- it may be that one of your classes is tickling the initialization of something seemingly unrelated to the crash)

bbum
There's no +initialize method on either EntryViewController or DateFieldController.
Billy Gray
Nope, no +initialize. Where/how do you want me to set the env var, in ~/.bash_login ? Or in one of the build settings...
Billy Gray
Look at your current target executable info in Xcode. Set the variable in the bottom pane of the "Arguments" tab.
ohhorob
Ah, dur. Interesting console output. Every class seems to get the full set of messages (i.e. calling... finished... ... fully +initialized). Right up to the moment I select that row on the table view, and boom, we get the same stack trace. So, I guess it's not even getting there, to the initialization stage.
Billy Gray
+1  A: 

The fact that the problem keeps moving around makes me suspect something is stomping on the runtime's internal class data structures. The first thing I'd suspect would be over-clever classes. You don't have any classes that play games with isa do you? See XMPPMessage.m for an example of this. I'm not asking if these view controller classes play with isa; I mean any class in the system since doing this wrong can cause it to stomp on memory in surprising ways.

Next I'd try using class_copyMethodList() and see if that crashes before calling +alloc. If so, then your class table has definitely been corrupted.

Rob Napier
I am frightened by your answer, but I'll look into it! Thanks.P.s. I'm not sure it's really moving around, the backtrace is exactly the same every time unless I "Enable Guard Malloc", which I believe forcibly uses different method calls in the runtime.
Billy Gray
Yeah, I'm not messing with the isa property on anything. DateFieldController is a descendant of DataFieldController, as is NotesFieldController. The one loads, the other folds. DataFieldController is simply a subclass of UIViewController. So, no funny business, I think. How do you mean, "using class_copyMethodList()", just step into it?
Billy Gray
I mean test with something like: "unsigned count; Method *methods = class_copyMethodList([NotesViewController class], " before calling +alloc and see if it crashes too. You'll need to #import <objc/runtime.h>. If that works, then the problem is likely elsewhere, but if that crashes, too, it means your internal tables are likely getting stomped on.
Rob Napier
Rob takes it, closest answer to what was going on. xcode project file garbage.
Billy Gray