2010-08-13

Overreleasing Objects

I'm preparing a course about iOS development and the intended pupils are all programmers so I'm looking for interesting issues to play with. One the most funniest bugs to hunt are those moving fast and unpredictably, such as those created by memory issues and threads. As my old ISP pulled off all my old contents I though it could be interesting to write here the example I prepared yesterday.

So, let's go for it.

First of all let's create a new project. It never mind which kind of project (for Mac OS X or iOS) because we are speaking about Objective C, but for this post I'll use a simple view-based iPhone App.

And now! I introduce you BuggyClass!!!

#import <Foundation/Foundation.h>

@interface BuggyClass : NSObject {
    NSString *text;
}

- (NSString *)giveMeTheText;

@end

Till here nothing special. Just an inoffensive class definition with a method returning an NSString and an attribute. Nice. Let's see the implementation:

#import "BuggyClass.h"

@implementation BuggyClass

- (id)init {
    if (self = [super init]) {
        text = [[NSDate date] description];
    }
    return self;
}

- (NSString *)giveMeTheText {
    return text;
}

- (void)dealloc {
    [text release];
    [super dealloc];
}

@end

Apparently an inoffensive class implementation too, though you can see the bug already if you read it.

We initialize the "text" property with an NSString and we return it when asked to do so. When the object is deallocated from the memory we release everything we created... in theory.

I'm omitting now all the process of creating a button with Interface Builder (Xcode 3.x and below) or Xcode 4, and binding it to an IBAction, so we goto see the code. Now, in our View Controller we could see something like this:

#import <UIKit/UIKit.h>

@interface SampleOverreleasing001ViewController : UIViewController {
 NSString *myVictimData; 
}

- (IBAction)crash:(id)sender;

@end

crash: is the cute action we'll call when the button became pressed. Obviously, if the application does not crash before that (and I will do it). The innocent victim (myVictimData) is just an attribute which is going to suffer our bug although doing its job properly. Let's see the simplified code to be stressed:

#import "SampleOverreleasing001ViewController.h"
#import "BuggyClass.h"

@implementation SampleOverreleasing001ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
 
    BuggyClass *myBuggyObject;
    myBuggyObject = [[BuggyClass alloc] init];
 
    myVictimData = [myBuggyObject giveMeTheText];
 
    NSLog(@"What it should be maintained in my object is: %@", myVictimData);
    
    [myBuggyObject release];
}

- (IBAction)crash:(id)sender {
    NSLog(@"Uh Ohhhh! loook at what happens with this object: %@", myVictimData);
}

@end


Guess what? If you try to run this it will fail miserably. But the funny part is:
  • When you use the Static Analyzer (clang) it shows it's OK.
  • When you use Instruments it doesn't show memory leaks -well, that's true ;)
  • If you enable NSZombies you get a hint, but not much.
  • If this were a multithreaded environment and our BuggyClass were on another thread or NSOperation this could be crazy.
So, lets see. You already know what's happening, don't you? Yeah, you read the title of the post: overreleasing an object so, somewhere, we are creating a memory issue. Well, actually Xcode give us the key too:

Yes! Program received signal:  “EXC_BAD_ACCESS” means that we are trying to use memory out of our control. So let's play with the code to understand what happened and many other variations.

Case 1: The code as is

The problem here is clear, but if you are new to Objective C probably you missed it. Look, when we store the value of text doing:

text = [[NSDate date] description];

actually we are pointing to an autoreleased object. This means that when Cocoa Touch (in this case) finds a drain message to the NSAutoreleasePool of the proper run loop, the retain count is decremented. In this case, as there's no other retain message, the object is deallocated. In other words, when you have an autoreleased object you have to expect it to be autoreleased sometime (nobody can say exactly when).

But that's not only the one problem. If you look at few lines below you'll see:

[text release];

And, as you can suppose, that's more than enough. In fact, it's too much releasing. The retain count for text (or the object pointed by text) was 1, after autoreleasing it it could be 0, but sending another release is illegal. We can not dealloc something already deallocated. Which deallocation is done before? the release or the autorelease? Well, if you are a good observer I put a trace on the previous screenshot to give you a hint. So, looking at the stack trace on the left and following the code you can see easily that release was called on first step and then it was autorelease (that's why the crashed object is kinda UIApplicationMain).

Case 2: overautoreleasing

What if we would written
[text autorelease];

instead of

[text release];

You may think the problem is the same and the error message is the same too. But that's not true. In this case you will receive the message: Program received signal:  “SIGABRT”.

Probably here Xcode is much more clear. You'll receive a message in the log similar to the next one:

sampleOverreleasing001(12641,0x3e7037c8) malloc: *** error for object 0x140560: double free
*** set a breakpoint in malloc_error_break to debug

I think it's clear enough. Just pass to the next case.

Case 3: retaining the overreleased

Let's go again to the first case when we were doing:
[text release];

[text release];

Suppose you know the problem is related to your victim object because you were using NSZombies. You can be tempted of retaining the problematic NSString. So you would do:
myVictimData = [[myBuggyObject giveMeTheText] retain];

instead of:
myVictimData = [myBuggyObject giveMeTheText];

Do you really think this is a good idea? Run the program and cry with me. One, two, three...
... mmmm ... it didn't crashed!

So, you have the great idea of pressing the button on the screen. That button invoking crash: and all you happiness fall down immediately: finally it crashed with the expected message: Program received signal:  “EXC_BAD_ACCESS”

Now the problem is again an overreleased object but with a little difference. In case 1 we tried to release a deallocated object, now we are trying to load data from the deallocated object. It's not the same thing, but very close.

Case 4: retaining the overautoreleased

This is the last case we'll see now, I promise. Now we are going to retain again the NSString returned by myBuggyObject but starting from the case 2 (when we autoreleased the text).

This case is, perhaps, the most interesting. This case depends a lot on the run loops and the execution order. This sample is too simple but a complex multithreaded environment may convert this case in a nightmare.

Do you think it will crash? Fine! not at the beginning. So we press the button to call crash: and... Great! It didn't crash neither. But here it is the worse thing. It didn't crash but if you see the console... well look at it:

2010-08-14 04:19:07.071 sampleOverreleasing001[12687:307] What it should be maintained in my object is: 2010-08-14 04:19:07 +0200
2010-08-14 04:19:07.077 sampleOverreleasing001[12687:307] BuggyClass dealloc
2010-08-14 04:19:48.213 sampleOverreleasing001[12687:307] Uh Ohhhh! loook at what happens with this object: ko.lproj

What's that! "ko.lproj"???? Your pointer is pointing to an unexpected part of the memory. As a result this kind of issue could be changing from execution to execution: sometimes it will generate exceptions (if you send a message it doesn't respond); other times you'll receive apparently good values (bad ones actually); other times you could receive unexpected results... that's not so easy to debug and for sure in a complex development it wont be funny.

The solution

Well, you know the best solution: Retain the initially autoreleased object as in:

text = [[[NSDate date] description] retain];

Exercise for hackers

Hackers as those described by Steven Levy, between others, understand me. Not criminals.

You now know a way of accessing memory beyond the strict limits you usually stay. May you find something interesting poking around? Tell us!


Note to me: next time name the BuggyClass as InnocentClass, KakklsfdQznjkr or something like that. These guys had too much meaningful names this time. ;)