Thread: [Pyobjc-dev] Bug Fixed!!! Dealing with alloc / init oddities
Brought to you by:
ronaldoussoren
From: Bill B. <bb...@co...> - 2002-10-24 14:32:15
|
(Another "debugging out loud" session -- but this one ended happily. I'll put the 'solution' first and those interested can read down for the details that led to it...) #if 1 self_obj = nil; if (*[methinfo methodReturnType] == _C_ID) { [inv setReturnValue:&self_obj]; } [inv setTarget:self_obj]; [inv setArgument:&self_obj atIndex:0]; #endif [inv release]; [methinfo release]; inv = nil; The above is found in objc_support.m around line 1423. By changing the "#if 1" to an "#if 0", the bug goes away and all unit tests pass (except invocation of class methods when an instance method of the same name also exists). The code #if'd in/out looks to be there to force the NSInvocation to clean up after itself. In theory, that should be handled by the [inv -release] on the line immediately after that block of code. In practice, this still shouldn't cause a crash assuming that the NSInvocation only retains things that it releases. Why the crash was happening isn't clear to me. The [lack of the above] code may also potentially be causing a memory leak? Change committed. --- I believe I have tracked the crasher w/the collection classes down to being related to the following behavior of NSArray: int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSArray *a; a = [NSMutableArray alloc]; NSLog(@"0x%x", a); a = [a init]; NSLog(@"0x%x", a); a = [NSMutableArray alloc]; NSLog(@"0x%x", a); a = [a init]; NSLog(@"0x%x", a); [pool release]; return 0; } The above outputs -- of note, If I change the second [NSMutableArray alloc] to [NSArray alloc], the output remains *unchanged*. 2002-10-24 09:35:37.398 barfoo[19606] 0x4b580 2002-10-24 09:35:37.424 barfoo[19606] 0x57c90 2002-10-24 09:35:37.439 barfoo[19606] 0x4b580 2002-10-24 09:35:37.452 barfoo[19606] 0x57cb0 Clearly, the Foundation/Core are playing games with the NSArray/NSMutableArray class clusters. The +alloc methods return placeholders that are turned into the appropriate instance upon initialization. This makes sense given that the -init* method used determines the role and, hence, the appropriate private subclass, of the instance of the class cluster. Now, consider the following: [bumbox:~/bbum-developer/sourceforge/pyobjc] bbum% python >>> from Foundation import * >>> NSArray.alloc().init() () >>> NSMutableArray.alloc().init() () >>> NSArray.alloc().init() 2002-10-24 09:37:46.189 python[19612] Did you forget to nest alloc and init? 2002-10-24 09:37:46.191 python[19612] *** Uncaught exception: <NSInvalidArgumentException> *** -length only defined for abstract class. Define -[NSPlaceholderMutableString length]! It appears that the bridge somehow caches the placeholder instance and ends up releasing or corrupting it upon the second call to the alloc() method *for that particular class*? Given that last bug, it seems like the shared placeholder array instance may be being released one time to many?? It is! (see above) |
From: Ronald O. <ous...@ci...> - 2002-10-25 20:06:39
|
Bill, The 'fix' was not your change to 'objc_support.m', but the change to 'selector.m' (the one I'm so unhappy about ;-() The problem is that NSMutableArray isn't really following the Cocoa refcounting conventions. This can be demonstrated using a simple Objective-C program: #import <Foundation/Foundation.h> int main(void) { id x; printf("class: %p\n", [NSMutableArray class]); printf("alloc: %p\n", x = [NSMutableArray alloc]); printf("init: %p\n", x = [x init]); printf("alloc: %p\n", x = [NSMutableArray alloc]); printf("init: %p\n", x = [x init]); } The output on my system: class: 0xa07ec7b8 alloc: 0x875a0 init: 0x8e7a0 alloc: 0x875a0 init: 0x8e7c0 Note that return value of the two calls of alloc is the same, and different from the return value of the corresponding init calls. Appearently 'alloc' just returns a kind of factory object, and 'init' is the one that actually allocates memory. Further investigation shows that the return value of 'alloc' is a 'NSPlaceholderMutableArray'. Actually it is probably that class and not an instance of it. Both 'x->isa->name' and 'x->isa->isa->name' are 'NSPlaceholderMutableArray', if 'x' where an object I'd expect the second to be 'NSObject'. I'm glad to find that the problem with NSArray is caused by a feature of Cocoa and is not a memory corrupting bug in PyObjC itself. That doesn't make it easier to actually fix it of course, but at least we can now start to think about how to really solve this instead of hunting down an elusive memory corruption error. Ronald |
From: Bill B. <bb...@co...> - 2002-10-25 21:00:56
|
On Friday, October 25, 2002, at 04:06 PM, Ronald Oussoren wrote: > Bill, > > The 'fix' was not your change to 'objc_support.m', but the change to > 'selector.m' (the one I'm so unhappy about ;-() Yes -- I undid the change in objc_support.m and committed the unmodified version. Sorry. Should have undid that. > The problem is that NSMutableArray isn't really following the Cocoa > refcounting conventions. This can be demonstrated using a simple > Objective-C program: That the NSArray class cluster uses a placeholder upon allocation isn't a violation of the Cocoa refcounting conventions -- it is just fallout from the design of the class cluster. There is nothing that says that what is returned by +alloc must be the same instance as whatever is returned by -init. > Note that return value of the two calls of alloc is the same, and > different from the return value of the corresponding init calls. > Appearently 'alloc' just returns a kind of factory object, and 'init' > is the one that actually allocates memory. > Further investigation shows that the return value of 'alloc' is a > 'NSPlaceholderMutableArray'. Actually it is probably that class and > not an instance of it. Both 'x->isa->name' and 'x->isa->isa->name' are > 'NSPlaceholderMutableArray', if 'x' where an object I'd expect the > second to be 'NSObject'. The NSArray class cluster returns a placeholder [factory] for allocation because it does not yet have enough information to determine which private subclass will most efficiently hold whatever the developer is about to throw at it. > I'm glad to find that the problem with NSArray is caused by a feature > of Cocoa and is not a memory corrupting bug in PyObjC itself. That > doesn't make it easier to actually fix it of course, but at least we > can now start to think about how to really solve this instead of > hunting down an elusive memory corruption > error. That the placeholder is both shared, but allows itself to be released to the point of destruction, would seem to be something that Apple would have wanted to avoid by simply overriding -release on the placeholder class and having it do nothing. This would, at least, prevent an app from crashing in a bizarre fashion because the cached placeholder instance has blown up. Then again, maybe they are playing interesting internal games with the retain count. The implementation of the placeholder class itself does seem to be a bit odd. b.bum |