I’m using NSData+Base64 opensrc library found on githubt for a project that is due in next month. I just started profiling, analyzing and optimizing, and found out a leak that comes from that code. I have disabled NSZombie and all debugging methods & try-catches, as I have previously found that sometimes they do leak memory.
The exact lines where the leak is pin-pointed is:
- (NSString *)base64EncodedString
{
return [self base64EncodedStringWithWrapWidth:0];
}
And the disassembly:
+0x0 pushl %ebp
+0x1 movl %esp, %ebp
+0x3 subl $24, %esp
+0x6 calll -[NSData(Base64) base64EncodedString]+0xb
+0xb popl %eax
+0xc movl +86389(%eax), %eax
+0x12 movl %eax, +4(%esp)
+0x16 movl +8(%ebp), %eax
+0x19 movl %eax, (%esp)
+0x1c movl $0, +8(%esp)
+0x24 calll DYLD-STUB$$objc_msgSend // 100% leak
+0x29 addl $24, %esp
+0x2c popl %ebp
+0x2d ret
I have not contacted the author, as I fear it may be my fault for not properly using the library, or just some other part of my code.
When I use the specific method (only use it once), I do so after an encryption routine:
+ (NSString *) encryptString:(NSString *)plaintext withKey:(NSString *)key
{
// Convert string-to-be-encrypted to Data
NSData *inData = [Miscellaneous utf8string2data:plaintext];
// Encrypt, Encode, Return
return [[self encryptData:inData withKey:key] base64EncodedString];
}
However, I get the feeling that the actual point where memory is leaking is in the method that is being called by method
base64EncodedStringWithWrapWidth
and the exact lines are:
outputBytes = realloc(outputBytes, outputLength);
NSString *result = [[NSString alloc] initWithBytesNoCopy:outputBytes length:outputLength encoding:NSASCIIStringEncoding freeWhenDone:YES];
So I guess my questions are:
1) Has anyone observed similar behavior using this library
2) Is it possibly me inducing the leak by using badly allocated strings
3) Anyone know how to solve it, or of another library I can use to replace this ?
Thanks!
EDIT:
I have changed the above lines that Instruments pinpoints as the leaking code to:
outputBytes = realloc(outputBytes, outputLength);
NSString *result = [[NSString alloc] initWithBytes:outputBytes length:outputLength encoding:NSASCIIStringEncoding];
free(outputBytes);
However, I still get a leak from that line. Is there an issue with malloc/realloc/free in objective C ?
EDIT 2:
Bytes Used # Leaks Symbol Name
512 Bytes 5.2% 2 thread_start
512 Bytes 5.2% 2 _pthread_start
512 Bytes 5.2% 2 __NSThread__main__
512 Bytes 5.2% 2 -[NSThread main]
512 Bytes 5.2% 2 -[Sync get]
512 Bytes 5.2% 2 -[Request DoRequest]
512 Bytes 5.2% 2 -[Request encryptMessage:]
512 Bytes 5.2% 2 +[AES256 encryptString:withKey:]
512 Bytes 5.2% 2 -[NSData(Base64) base64EncodedString]
512 Bytes 5.2% 2 -[NSData(Base64) base64EncodedStringWithWrapWidth:]
512 Bytes 5.2% 2 -[NSPlaceholderString initWithBytes:length:encoding:]
512 Bytes 5.2% 2 CFStringCreateWithBytes
512 Bytes 5.2% 2 __CFStringCreateImmutableFunnel3
512 Bytes 5.2% 2 _CFRuntimeCreateInstance
512 Bytes 5.2% 2 CFAllocatorAllocate
512 Bytes 5.2% 2 __CFAllocatorSystemAllocate
I don’t know if it will help identify whether this is a false warning or not, but by not filtering anything, the culprit appears to be CFAllocatorSystemAllocate:
+0x13 calll DYLD-STUB$$malloc_zone_malloc
+0x18 addl $8, %esp
So I am starting to have reservations wether this really is a leak. However debugging on both the emulator & iPad has the same results. ARC is used, and I’m trying to copy objects instead of referencing them.
SOLUTION:
I have found the leak, it was in a method implemented improperly using the wrong kind of cast ( silly me I didn’t see this the first time )
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(__bridge_retained CFStringRef)string,
NULL,
(CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ",
CFStringConvertNSStringEncodingToEncoding(encoding));
bridge_retain here incremented the ARC by one, and thus it wasn’t being released, and thats why I was getting the warning. Funnily it was the last piece of code I looked at, before I realized what was wrong. Thank you for your help
You are probably right in your analysis that the leak comes from the place where
outputBytesis reallocated. However, note that the “real” culprit for the leak is thatoutputBytesis not freed in the end. The question is why it is not freed.If you take a look at the next line,
outputBytesis fed intoNSStringdirectly without copying the contents of the string it points to, so I guess that the intention of the author was thatNSStringtakes ownership of theoutputBytesmemory block from then on, and it is the responsibility ofNSStringto free it.NSStringwill free this block when its reference counter becomes zero. The next few lines in the code show that theNSStringbecomes auto-released:If
NSStringis auto-released, then everything should be fine — the ownership ofNSStringis taken by the autorelease pool, and you should not have toreleaseit on your own when you use it. The above code snippet also says thatautoreleaseis called only if you are compiling the code without ARC (automatic reference counting). So, I think that the code is correct, therefore the only reason whyoutputBytescould be leaked is because you are retaining the returnedNSStringsomewhere and then forget to release it. In other words, the leaked memory is allocated within the library you are using, but the reason why it is not freed lies outside the library and the library itself cannot be made responsible for the leak.Your
encryptStringmethod seems to take the encoded string and pass it along to its caller, not changing its reference count anywhere, so you have to keep on looking at places where you are callingencryptStringto see if there is any place where you are accidentally retaining the string and not releasing it later.