I'm attempting to narrow down a bug to a minimum reproducible case and found something odd.
Consider this code:
static NSString *staticString = nil;
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
if (staticString == nil) {
staticString = [[NSArray arrayWithObjects:@"1", @"2", @"3", nil] componentsJoinedByString:@","];
}
[pool drain];
NSLog(@"static: %@", staticString);
return 0;
}
I'm expecting this code to crash. Instead, it logs:
2011-01-18 14:41:06.311 EmptyFoundation[61419:a0f] static: static:
However, if I change the NSLog()
to:
NSLog(@"static: %s", [staticString UTF8String]);
Then it does crash.
edit a bit more info:
After draining the pool:
NSLog(@"static: %@", staticString); //this logs "static: static: "
NSLog(@"static: %@", [staticString description]); //this crashes
So apparently invoking a method on the string is good enough to get it to crash. In that case, why doesn't logging the string directly cause it to crash? Shouldn't NSLog()
be invoking the -description
method?
Where is the second "static: " coming from? Why isn't this crashing?
Results:
Both Kevin Ballard and Graham Lee are correct. Graham's correct in realizing that NSLog()
is not invoking -description
(as I was erroneously assuming), and Kevin is almost definitely correct that this is a weird stack-related issue with copying a format string and a va_list
around.
NSLogging
andNSString
does not invoke-description
. Graham elegantly showed this, and if you trace through the Core Foundation sources that do the logging, you'll see that this is the case. Any backtrace originating insideNSLog
shows that it invokesNSLogv
=>_CFLogvEx
=>_CFStringCreateWithFormatAndArgumentsAux
=>_CFStringAppendFormatAndArgumentsAux
._CFStringAppendFormatAndArgumentsAux()
(line 5365) is where all of the magic is going on. You can see that it's manually going through to find all the%
substitutions. It only ends up invoking the description copy function if the type of the substitution is aCFFormatObjectType
, the description function is non-nil, and the substitution hasn't already been handled by another type. Since we've shown that the description is not getting copied, it's reasonable to assume that anNSString
gets handled earlier (in which case it's probably going to be doing a raw byte copy), which leads us to believe...- There's a stack error going on here, as Kevin surmises. Somehow the pointer that was pointing to the autoreleased string is getting substituted to a different object, which happens to be an
NSString
. So, it doesn't crash. Weird. However, if we change the type of the static variable to something else, like anNSArray
, then the-description
method does get called, and the program does crash as expected.
How truly and utterly strange. Points go to Kevin for being the most correct about the root cause of the behavior, and kudos to Graham for correcting my fallacious thinking. I wish I could accept two answers...
NSLog([NSString stringWithFormat:@"static: %@", staticString])
does cause a crash, so it's really due to a different behavior ofNSLog
when handling%@
– Salicylate_CFStringAppendFormatAndArgumentsAux
is incorrect. For each%@
,_CFStringAppendFormatAndArgumentsAux
will try 1) acopyDesc
function that was passed in via an argument, 2)__CFCopyFormattingDescription
, which for ObjC objects results in trying_copyFormattingDescription:
, and finally 3)CFCopyDescription
, which for ObjC objects results in_copyDescription
, and disassembly showsNSObject
defaults to calling-description
. Therefore, 99% of the time,%@
will result in-description
being called. – Phosphide__CFCopyFormattingDescription
andCFCopyDescription
end up doing special CF stuff. Each CF object has an entry inCFRuntimeBase
forcopyFormattingDesc
andcopyDebugDesc
(which correspond to the previously mentioned functions). For aCFString
object, this amounts to calling__CFStringCopyFormattingDescription
, which does nothing more than aCFStringCreateCopy
of the string. – Phosphide_CFStringAppendFormatAndArgumentsAux
a%@
for "strings" (most of which areCFString
objects in practice) amounts to callingCFStringCreateCopy
, the result of which isCFStringAppend
'ed to the mutable string result being built up. – Phosphide-description
is not getting called when loggingNSString
objects, which means that some short circuiting is happening somewhere. I was thinking that it would be called, and that assumption was leading me to expect the crash. – FrohmanNSString
objects, or Core FoundationCFString
objects masquerading via toll-free bridging asNSString
objects? If it's the later, then it's non-sensical to talk about-description
being called- you need to look at (CF-550)CFString.c:5746
->CFRuntime.c:799
->CFString.c:1141
->CFString.c:1126
->CFString.c:5752
for the Core Foundation internal/private "equivalent" for-description
of aCFString
object. – Phosphide-description
is definitely getting called, it's just being "automagically" suppressed byNSLog()
because (I assume, probably has to do with multithreaded locking issues)NSLog()
blocks (i.e., returns immediately without doing any work) additional calls toNSLog()
while aNSLog()
is currently being processed. – Phosphide