0
votes

I have a function which use for read one single line from a csv file. But I got a release of previously deallocated object error, or sometimes the it is "double free" error.

I try to track down which object causes this error base on the error memory address, but I failed to do this.

Here's the code:

    @interface CSVParser : NSObject {
    NSString *fileName;
    NSString *filePath;
    NSString *tempFileName;
    NSString *tempFilePath;

    //ReadLine control
    BOOL isFirstTimeLoadFile;
    NSString *remainContent;
}

@property(nonatomic,retain) NSString *fileName;
@property(nonatomic,retain) NSString *filePath;
@property(nonatomic,retain) NSString *tempFileName;
@property(nonatomic,retain) NSString *tempFilePath;

@property(nonatomic,retain) NSString *remainContent;

-(id)initWithFileName:(NSString*)filename;

-(BOOL)checkAndCopyFile:(NSString *)filename;
-(BOOL)checkAndDeleteTempFile;
-(NSString*)readLine;
-(NSArray*)breakLine:(NSString*)line;

@end

@implementation CSVParser

@synthesize fileName;
@synthesize filePath;
@synthesize tempFileName;
@synthesize tempFilePath;

@synthesize remainContent;

-(id)initWithFileName:(NSString *)filename{
    //ReadLine control
    isFirstTimeLoadFile = TRUE;

    self.fileName = filename;
    self.tempFileName = [[NSString alloc] initWithFormat:@"temp_%@",fileName];
    NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDir = [documentPaths objectAtIndex:0];
    self.filePath = [documentDir stringByAppendingPathComponent:fileName];
    self.tempFilePath = [documentDir stringByAppendingPathComponent:tempFileName];
    if ([self checkAndCopyFile:fileName]) {
        return self;
    }else {
        return @"Init Failure";
    }

}

-(BOOL)checkAndCopyFile:(NSString *)filename{
    BOOL isFileExist;
    NSError *error = nil;
    NSFileManager *fileManger = [NSFileManager defaultManager];
    isFileExist = [fileManger fileExistsAtPath:filePath];
    if (isFileExist) {
        //Create a temp file for reading the line.
        [fileManger copyItemAtPath:filePath toPath:tempFilePath error:&error];
        return TRUE;
    }else {
        return FALSE;
    }
}

-(NSString*)readLine{
    NSError *error = nil;
    //Read the csv file and save it as a string
    NSString *tempFirstLine = [[[NSString alloc] init] autorelease];
    NSString *stringFromFileAtPath = [[NSString alloc] init];
    if (isFirstTimeLoadFile) {
        NSLog(@"Into First Time");
        stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath 
                                                         encoding:NSUTF8StringEncoding 
                                                            error:&error];
        isFirstTimeLoadFile = FALSE;
    }else {
        NSLog(@"Not First Time");
        NSLog(@"Not First Time count:%d",[remainContent retainCount]);
        stringFromFileAtPath = remainContent;
        remainContent = nil;
    }
    if ([stringFromFileAtPath isEqualToString:@""]) {
        [stringFromFileAtPath release];
        return @"EOF";
    }

    //Get the first line's range
    NSRange firstLineRange = [stringFromFileAtPath rangeOfString:@"\n"];
    //Create a new range for deletion. This range's lenght is bigger than the first line by 1.(Including the \n)
    NSRange firstLineChangeLineIncludedRange;
    if (stringFromFileAtPath.length > 0 && firstLineRange.length == 0) {
        //This is the final line.
        firstLineRange.length = stringFromFileAtPath.length;
        firstLineRange.location = 0;
        firstLineChangeLineIncludedRange = firstLineRange;
    }else {
        firstLineRange.length = firstLineRange.location;
        firstLineRange.location = 0;
        firstLineChangeLineIncludedRange.location = firstLineRange.location;
        firstLineChangeLineIncludedRange.length = firstLineRange.length + 1;
    }
    //Get the first line's content
    tempFirstLine = [stringFromFileAtPath substringWithRange:firstLineRange];
    remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange withString:@""];

    [stringFromFileAtPath release];
    error = nil;
    return tempFirstLine;
}

And the following code shows how I use the class above:

CSVParser *csvParser = [[CSVParser alloc] initWithFileName:@"test.csv"];
BOOL isFinalLine = FALSE;

while (!isFinalLine) {
    NSString *line = [[NSString alloc] init];
    line = [csvParser readLine];
    if ([line isEqualToString:@"EOF"]) {
        isFinalLine = TRUE;
    }
    NSLog(@"%@",line);
    [line release];
}
[csvParser release];

If I run the code, and finish the csv parsing, the App's main function will give me the double free error when it try to free the autorelease pool."* __NSAutoreleaseFreedObject(): release of previously deallocated object (0x6a26050) ignored"

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, nil);

Could someone help me solve this issue? Thank you! [pool release];

3
Might I suggest a more full-featured CSV Parser? github.com/davedelong/CHCSVParserDave DeLong

3 Answers

2
votes

Do not use -retainCount.

The absolute retain count of an object is meaningless.

You should call release exactly same number of times that you caused the object to be retained. No less (unless you like leaks) and, certainly, no more (unless you like crashes).

See the Memory Management Guidelines for full details.


There are a few problems in your code:

  • you aren't following the correct init pattern. You should have a self = [super init...]; if (self) {...} in there somewhere.

  • tempFileName is a retain property and you assign it the result of alloc/init. It will be leaked.

  • An immutable empty string ([[NSString alloc] init]) is pretty much never useful. And, in fact, stringFromFileAtPath is being leaked (technically -- implementation detail wise there is an empty immutable singleton string and thus, no real leak, but.... still...)

  • Finally, the crash: your readLine method correctly returns an autoreleased object. Yet, your while() loop consuming the return value of readLine is also releaseing that return value, leading to a double-release and an attempt to free that which was already freed.

You should "build and analyze" your code. I bet the llvm static analyzer would identify most, if not all, of the problems I mentioned above (and probably some more I missed).


When building with the analyzer, do you have either "all messages" or "analyzer issues only" selected in the Build window? Because, looking at the code, I'm surprised the analyzer didn't catch the obvious problem with stringFromFileAtPath.

Excerpting the code, you have the following lines that manipulate stringFromFileAtPath:

NSString *stringFromFileAtPath = [[NSString alloc] init];
....
stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath 
                                                 encoding:NSUTF8StringEncoding 
                                                     error:&error];
....
stringFromFileAtPath = remainContent;
....
[stringFromFileAtPath release];

And remainContent is set by:

remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange
                                                              withString:@""];

You are releasing an autoreleased object. By memory keeps going up, how are you measuring it? Don't use Activity Monitor as it is nearly as useless to developers as retainCount is misleading. Use Instruments.

0
votes

Your tempFirstLine NSString object is declared with autorelease, and is returned as your NSString line, which is then released.

Try using this:

while (!isFinalLine) {
NSString *line = [csvParser readLine];
if ([line isEqualToString:@"EOF"]) {
    isFinalLine = TRUE;
}
NSLog(@"%@",line);
}
0
votes

Replac this:

NSString *stringFromFileAtPath = [[NSString alloc] init];

with this:

NSString *stringFromFileAtPath = nil;

and get rid of the [stringFromFileAtPath release] statements.

The first line creates a pointer to a new string object that you never use, because you immediately overwrite the pointer with a pointer to string objects from elsewhere, which you don't need to release because you don't own them/didn't create them. Since you are releasing them, you're getting a crash.

You make the same mistake with tempFirstLine.