You Heard The Man, Blow Your Brains Out!
Posted by pookzilla on November 20, 2007
So you’ve read Steve’s blog and you’re all excited to help out with the Cocoa port (as you should be). But then you grab the code and you’re worried. Just how do you do anything with this? This is all so new and rough. You’re plodding along, kicking the tires, and suddenly you’re assaulted by a segfault in the native library. Your pant burst into flame, your dog gives birth to kittens, and you start singing soprano. Where do you start? How do you track this issue down?
There are going to be crashes. When you come across one that smells like a segfault here’s a simple (if somewhat laborious) method to track it down.
- open NSObject and put the following line at the beginning of release():
System.out.println("Release: " + toString()); - launch and make the crash happen
- In your Console view you should see reams and reams of lines like this:
...
Release: org.eclipse.swt.internal.cocoa.NSBezierPath@dd4cd3
Release: org.eclipse.swt.internal.cocoa.NSAttributedString@deb323
Release: org.eclipse.swt.internal.cocoa.NSBezierPath@c2ee77 - Starting from the bottom up locate each subclass of NSObject (such as NSBezierPath) and either override it’s
release()method such that it’s empty or alter the existingrelease()method so that it does nothing. - repeat steps 2-4 with each successive subclass you encounter until the crash stops happening. Something pertaining to the last class you changed is probably your culpret.
- restore the
release()method on the subclass to its original form so that the crash happens again. Remove theSystem.out.printlnas well if you no longer feel it’s helpful to you. - find all callers to
release()on the subclass. - from here it gets fuzzy. You’ll need to start commenting out calls to
release()and see if any particular disposal of the object is responsible. Perhapsrelease()is called but the object is still held in SWT and later used for another purpose. If commenting out any particularrelease()call doesn’t stop the crash from happening you start looking at how the object is being retained. If we’re using an object from another structure are we callingretain()orcopy()on it? We should never berelease()-ing anything we haven’t previouslyretain()‘d (explicitly or implicitly)
Here’s how I applied this technique to find the crash Steve alluded to that was preventing Eclipse from coming up. I added my System.out statement to NSObject.release() and started Eclipse. It crashed and the last thing to be released was NSAutoReleasePool. I added an empty release() method to this class and tried to launch again. This time it crashed on NSBezierPath. I added a no-op release() to NSBezierPath and tried again. No crash! I then reverted NSAutoReleasePool and turned my attention towards callers to NSBezierPath.release().
I found two of interest in GC and another in Path. I decided to look at GC first because it seemed more interesting than Path. I commented out one of the releases in GC.setClipping() and launched only to find that the crash persisted. I then commented out the one in GC.dispose() and still the crash persisted. This one was going to be a bit more difficult than a simple disposal. I turned my attention towards all the places we set the data.clipPath member (the member that contained a reference to an NSBezierPath). I discovered two places where we set the value of data.clipPath: we null it out in GC.dispose() after calling release() on it and we
both null it out and assign it in setClipping. I decide to look in setClipping() first because the dispose() looks innocuous enough.
In setClipping we’re releasing the previous value of data.clipPath, if any. We then go and assign the supplied NSBezierPath to the data.clipPath member. Hmm. Interesting. We’re releasing an object we haven’t retained in this method. We must be retaining it elsewhere, right? Let’s look. There are four callers to the setClipping() method that takes an NSBezierPath. The first, taking four integer arguments, constructs and retains a new NSBezierPath so that isn’t our culprit. The second, taking a Path, creates a copy() of the path NSBezierPath instance (which is implicitly retain()‘d). This one is probably good too. Next up at bat is setClipping that takes a Region as input. This one looks suspicious! Here we’re passing in the NSBezierPath of the Region object without retaining it. Decrementing the reference count of an object you haven’t previously increased the reference for is a surefire way of causing grief. If we simply add a retain() or copy() call to the NSBezierPath object we’re getting from Region.getPath() the crash should go away. And lo it does.
This virgin Cocoa port represents an incredibly awesome opportunity for the community to shine. People have been crying for a Cocoa port for years now and now there’s an opportunity for industrious contributors to grab the bull by the horns and make it happen. The rewards (in terms of good will alone) are enormous. The SWT team has done a remarkable job bootstrapping the effort to get this port underway and it would be a real shame if the community didn’t jump on board and make it happen. Long term we absolutely NEED Cocoa for continued existence on the Mac – Carbons days are numbered. If you’re an Eclipse user on the Mac you owe it to yourself and the community to participate in this as best you can. If you don’t feel you’re able to submit patches then well-investigated bug reports will do. This is a huge amount of work and every little bit helps!









