Objective-C thread safe code to prevent a crash - objective-c

I got an iPhone crash report with a SIGSEGV and I think I've narrowed down the possible cause and a solution.
Since crashes caused by threads are hard to debug I can't repro this problem, but could use some help with my hypothesis - is it sound?
My code uses ASIHttpRequest to download a set of files using an ASINetWorkQueue. Here is a simplified sample
//initialize download queue and do this code block in a loop for each file
NSURL *fileURL = [NSURL URLWithString:...
__block ASIHTTPRequest *fileRequest = [ASIHTTPRequest requestWithURL:fileURL];
[fileRequest setCompletionBlock:^{
//do some stuff
}];
[fileRequest setFailedBlock:^{
NSString *someError = [NSString stringWithFormat:...
[self someErrorMethod:someError];
}];
[downloadQueue addOperation:...
-(void)someErrorMethod(NSString *errorMessage) {
DDLogWarn(errorMessage);
if ([self downloadQueue]) {
for (ASIHTTPRequest *request in [[self downloadQueue] operations]) {
[request clearDelegatesAndCancel];
}
[[self downloadQueue] reset];
}
}
The top 2 lines of the crash report are
libobjc.A.dylib 0x31846fbc objc_msgSend + 15
MyApp 0x0002cab5 -[Myapp someErrorMethod:] (MyApp.m:)
My thinking for why this happened
A file download fails and the failed block is called
It goes through every request and clears delegates and cancels them and then resets the queue
However, while it is till running, another file download fails and enters the failed block callback
However, since it has now been cancelled, its failed block has been released
When the code tries to log the error message, its memory has been released and unpredictable results follow
Does this make sense? Since I am newish to Objective-C, is my analysis correct or am I missing something obvious?
I am thinking of using a lock to make the errorMethod thread safe with the hope that it will fix this issue. Does that sound like the right solution based on the code above?
Thanks

This doesn't sound likely. ASIHttpRequest likely performs all of its callbacks on the same thread (I'm fairly certain on this one).
If I had to guess, your error is more likely in this line:
DDLogWarn(errorMessage);
The first parameter to DDLogWarn is a format, not a string. This will likely crash in any case that errorMessage includes a %. What you meant is:
DDLogWarn(#"%#", errorMessage);
Since DDLogWarn() is a varags method, it will start substituting the (random) values it finds on the stack for any % substitutions in the string. It will read the stack until you run out of % substitutions. If any of the % substitutions are pointer-based (like %s or %#), then it will follow the pointer to a random location.
SEG_ACCERR means that you've requested a piece of memory you don't own. SEG_MAPERR means you've requested a piece of memory that is not mapped. Either is an expected result of following a totally random pointer.

Related

NSOperationQueue methods parameter are becoming empty

I am having problem with NSOperationQueue, if I am adding the same operation for 200 times method is behaving as expected.
But if I increase the for loop to 500 times, parameter are becoming empty when queue will start executing the task. Below is the code snippet.
- (void)someMethod:(char *)param1 {
NSBlockOperation *theOp = [NSBlockOperation blockOperationWithBlock: ^{
// use this paramter and do something
}];
[[MyQueueService sharedMyQueueService] operationQueue] addOperation:theOp];
}
This is how I am invoking the above method
for (int index = 1; index < 500; index++) {
MyClass *classInstance = [[MyClass alloc] init];
NSString *parm1 = [NSString stringWithFormat:#"%d", index];
[classInstance someMethod:(char *)[string cStringUsingEncoding:NSUTF8StringEncoding]];
}
Here is becoming empty i.e "", if i run the same method for 500 time, due to this I am unable to perform other operation. Please help me regarding this.
The problem is not with NSOperationQueue. The issue is the use of char *. As the documentation for cStringUsingEncoding says:
The returned C string is guaranteed to be valid only until either the receiver is freed, or until the current memory is emptied, whichever occurs first. You should copy the C string or use getCString:maxLength:encoding: if it needs to store the C string beyond this time.
Bottom line, simple C pointers like char * do not participate in (automatic) reference counting. Your code is using dangling pointers to unmanaged buffer pointers. This is exceedingly dangerous and when not done properly (as in this case), will lead to undefined behavior. The resulting behavior is dictated whether the memory in question happened to be reused for other purposes in the intervening time, which can lead to unpredictable behavior that changes based upon completely unrelated factors (e.g. the loop count or whatever).
You should try running your app with the "address sanitizer" turned on (found in the scheme settings under the "run" settings, on the diagnostics tab) and it will likely report some of these issues. E.g. when I ran your code with address sanitizer, it reported:
==15249==ERROR: AddressSanitizer: heap-use-after-free on address 0x60300003a458 at pc 0x00010ba3bde6 bp 0x70000e837880 sp 0x70000e837028
For more information, see Address Sanitizer documentation or its introductory video.
The easiest solution is going to be to eliminate the char * and instead use the int index value or use an object, such as NSString *.

Debugger stops on core data save but no error output

I have a simple method that just takes two managed object IDs, pulls the managedObjects for them, makes a relation and then saves them into the managedObjectContext.
When I perform the save on the managedObjectContext, the debugger stops on the save line with an objc_exception_throw referencing the nsmanagedObjectContext save. Although there is no output in the nserror object that is output to give me any details as to why an exception is thrown. It also appears as if this save does work fine, which makes this even more confusing.
Here is the method in question...
- (void)relateLocationToInvite:(NSManagedObjectID *)locationID :(NSManagedObjectID *)inviteID {
NSManagedObject *invite = [self.managedObjectContext objectWithID:inviteID];
NSManagedObject *locationObj = [self.managedObjectContext objectWithID:locationID];
Location *location = (Location *)locationObj;
[invite setValue:location forKey:#"location"];
NSError *error = nil;
if( ![self.managedObjectContext save:&error] ){
NSLog(#"Error relating a location to an invite %#",error);
}
}
If the execution of the application continues after the save: method without any problem (i.e. without throwing any uncaught exception and without reporting any error), that means the saving operation's implementation has caught the exception and decided to silently ignore it.
Why this happens is unclear: maybe the implementation relies on exceptions to report internal errors (this is not the way Objective-C exceptions should be used but some other languages make more use of exceptions). As long as the exception is caught before it reaches your own code, you should not worry about it.
If you want to know the reason of the exception, you may break on objc_exception_throw and use the following debugger command:
po *(id *)($ebp + 8)
This will display the NSException * parameter given to the function in the iOS Simulator (x86 architecture). On the device (arm architecture), the same result can be achieved using (if my memory serves my correctly):
po $r0

Object released with CFRelease causes obvious crash, but only rarely

I have the following method:
+ (NSString*) getMD5HashFromFile:(NSString*)filePath {
CFStringRef md5hash = FileMD5HashCreateWithPath((CFStringRef)filePath, FileHashDefaultChunkSizeForReadingData);
NSString *hashStr = (NSString*)md5hash;
CFRelease(md5hash);
return hashStr;
}
I was getting random crashes on the Simulator, about 1 in 20-30 executions. The fact that this wasn't consistent didn't help me dig deeper before.
Now that I see the code again, it seems obvious that md5hash gets released before being returned, which means the returned object is invalidated. The returned value is used in another method in a consistent way that crashes sometimes, but not always. My question is why this only happens rarely and not always.
Does it have something to do with the mix of Obj-C and C code and the way autorelease pools work?
Note: The bug seems to be fixed by using NSString *hashStr = [NSString stringWithString:(NSString*)md5hash], which makes total sense to me.
Just because a piece of memory is released and deallocated doesn't mean that it's immediately returned to the OS. Your application can hold onto it for an arbitrary period of time based on numerous factors and at several layers. The OS has more important things to do sometimes than reclaim every piece of memory you let go of and might ask for again in half a second. Accessing memory that you've called free() on, but technically own, does not generate a signal. This is why MallocScribble exists. It overwrites memory that you free with trash (0x55) so that it's more obvious when you use freed memory.
Try the following:
char *foo = malloc(100);
strcpy(foo, "stuff");
free(foo);
printf("%s", foo);
Most of the time that'll work fine, despite being completely wrong. Now, edit your Scheme>Diagnostics and Enable Scribble. Re-run and you'll see a bunch of "U" (0x55) indicating that you're reading nonsense. But it still won't crash.
You may be interested in Matt Gallagher's A look at how malloc works on the Mac for a bit more on the topic.
CFRelease argument must not be NULL.
If CFRelease argument is NULL, this will cause a runtime error and
your application will crash
if(md5hash)
CFRelease(md5hash);
+(NSString*) getMD5HashFromFile:(NSString*)filePath {
CFStringRef md5hash = FileMD5HashCreateWithPath((CFStringRef)filePath, FileHashDefaultChunkSizeForReadingData);
NSString *hashStr = [(NSString*)md5hash copy];
CFRelease(md5hash);
return [hashStr autorelease];
}
make sure to retain the returned value in the caller if you need to hang on to it for any length of time.

Why I'm getting memory leaks with xmlTextReaderConstValue?

I'm writing my own wrapper class for parsing XML data. Usually I use the Leak Performance Tool to detect suspicios behaviour through forgetting to release allocated memory.
At this time I figured out that the following code (the first line becomes marked by the tool) brings me an enormous memory leak (leaks more the bigger the XML data file becomes).
the following part is used to receive the text inside a Node.
NSString *currentTagValue = [NSString stringWithCString:(char *)xmlTextReaderConstValue(XMLReader) encoding:NSUTF8StringEncoding];
SEL selector = NSSelectorFromString([NSString stringWithFormat:#"set%#:", [currentTag capitalizedString]]);
[currentItem performSelector:selector withObject:currentTagValue];
If I add
[currentTagValue release]
the memory leaks are gone.
This seems strange to me, because I don't allocate memory for the NSString manually. That's why I thought it would be autoreleased.
The whole situation becomes stranger if I compare the upper code example with the part that is responsible for obtaining the node name.
NSString *currentTagName = [NSString stringWithCString:(char *)xmlTextReaderConstName(XMLReader) encoding:NSUTF8StringEncoding];
SEL selector = NSSelectorFromString([NSString stringWithFormat:#"set%#:", [currentTagName capitalizedString]]);
Here I dont't have to add a manual release, everything works fine and I'm getting no memory leak.
I'm not sure if my described problem is a side-effect of the xml...ConstValue function (the working part uses xml...ConstName) or if the reason is the performed selector afterwards.
Thanks for reading, I hope anyone can explain it to me.
Are you using libxml2? I haven't used libxml2 yet, but I googled quickly and found this:
http://xmlsoft.org/html/libxml-xmlreader.html
Function: xmlTextReaderConstValue
Returns: the string or NULL if not
available. The result will be
deallocated on the next Read()
operation.
Compare that with xmlTextReaderConstName
Function: xmlTextReaderConstName
Returns: the local name or NULL if not
available, the string is deallocated
with the reader.
It may be a leak in the lib, or a false alarm as the result seems to be on a delayed release (or something entirely different as I have no firsthand experience to say otherwise). Is the program crashing because of the leak or not? If it is not, maybe it's just a false alarm.
Hope it helps.

What does NSLog actually do?

I am having a weird problem. I am using a method from Apple's private frameworks in my application. When I call it for the first time, it works. When I call it for the second time immediately without anything in between, it crashes. However, if I put NSLog between the two calls, it works wonderfully. So I try removing NSLog and puting for-loops, sleep(), printf("..."), and fprintf(stderr, "...") between them to emulate NSLog, but it doesn't help. I am wondering how the method knows that I use NSLog? In other words, what does NSLog actually do to affect the behaviors of the method?
Thank you very much!
EDIT:
I seem to solve this problem. I will share my solution here and hope it may be useful to some people.
I am creating a multitouch-related application using MultitouchSupport.framework. I copied code from http://aladino.dmi.unict.it/?a=multitouch and added a CFRelease at the end of the loop. So, basically, my main method looks like this :
int main(void) {
int i;
NSMutableArray* deviceList = (NSMutableArray*)MTDeviceCreateList(); //grab our device list
for(i = 0; i<[deviceList count]; i++) { //iterate available devices
MTRegisterContactFrameCallback([deviceList objectAtIndex:i], touchCallback); //assign callback for device
MTDeviceStart([deviceList objectAtIndex:i], 0); //start sending events
}
CFRelease((CFMutableArrayRef)deviceList);
printf("Ctrl-C to abort\n");
sleep(-1);
return 0;
}
After running for a while, it will show "Program received signal: “EXC_BAD_ACCESS”."
And here is the stack trace:
#0 0x7fff8795496e in ParsedMultitouchFrameRepInitialize
#1 0x7fff879565b1 in mt_HandleMultitouchFrame
#2 0x7fff87955a03 in mt_DequeueDataFromDriver
#3 0x7fff87955b29 in mt_DequeueMultitouchDataFromDriverThreadEntry
#4 0x7fff831b3456 in _pthread_start
#5 0x7fff831b3309 in thread_start
However, if I put NSLog below MTDeviceStart, it will not crash.
The reason I added CFRelease((CFMutableArrayRef)deviceList) to the original code is that I think objects that are created from functions named *Create* or *Copy* should be released by ourselves. But it turns out that if I remove it like the original code does, it will not crash, even without using NSLog.
So, maybe it's because I release deviceList too early? But if that's so, why does NSLog seem to be able to prevent the crash?
Something similar to this:
static inline void NSLogMessageString(NSString *string){
NSString *date=[[NSDate date]
descriptionWithCalendarFormat:#"%Y-%m-%d %H:%M:%S.%F"
timeZone:nil locale:nil];
NSString *process=[[NSProcessInfo processInfo] processName];
NSLogFormat(#"%# %#[%d:%lx] %#",date,process,NSPlatformProcessID(),NSPlatformThreadID(),string);
}
void NSLogv(NSString *format,va_list arguments) {
NSString *string=NSStringNewWithFormat(format,nil,arguments,NULL);
NSLogMessageString(string);
[string release];
}
void NSLog(NSString *format,...) {
va_list arguments;
va_start(arguments,format);
NSLogv(format,arguments);
}
Thanks for asking this question lol, I wanted to rewrite it so I could add debugging variables, meaning I could turn all NSLogging calls off when needed..
It takes a long time. I'm not sure why. It prints the date/time, process name, process ID, thread ID, and (finally) the string you asked for. I think it also sends the log message to syslogd (either Xcode or iPCU's console shows multiline NSLogs as a single entry; I forget which); the IPC there might be significant.
Try using syslog() (#import <syslog.h> and then syslog(LOG_INFO, "Hello there!");, if it works but you get no output, try changing the priority (see man 3 syslog).
NSLog can affect issues like the one you are running into because it affects the order that threads execute because when you call NSLog in a background thread, it has to gain exclusive access to stdout. printf debugging tricky problems with threads often leads to "heisenbugs" for this reason (i.e. they change behavior when you try to examine them).
It could be a problem with memory management: an extraneous release perhaps. If you post the traceback, it might be some help in tracking down the issue. (As it turns out, someone on Twitter I follow mentioned something like this last night).