2012-01-17 after work: updated for ranty tone, petulance, spelling
This post describes a bug in Xcode 4.2.1, and a way to work around it. If you find it useful (for debugging the source code of your IDE, say) please come to my house and clean the grout in my shower.
This is not what a day’s work should look like:
Today I enountered a [expletive] bug in Xcode, uncomfortably similar to the last serious① Xcode bug that submitted me via rear naked choke.
Executive summary:
Xcode 4.2.1 (and I think maybe all 4.x versions so far released) will shit all over itself and become unable to edit .xib files, if there is header file that exists somewhere in your source tree (even one not actually included in your project), that contains an unterminated comment in it.
The symptoms are that you add an outlet to a class in code (e.g. in a MyViewController.h), but the outlet does not appear in the UI editor. So you cannot connect the outlet to whatever view or entity it is supposed to point to. If you quit and re-launch Xcode it tends to fix the problem. But you will end up having to quit and re-launch Xcode every single time you add any outlet to anything.
That’s a workaround much like eating a piece of dogshit is a workaround for being hungry.
You can look at the bug reproduction demo project for an example.
That’s it. You can stop reading here. If you can’t edit .xib files in Xcode4, look for a stray header in your source tree, and not in your project, that has an unterminated comment in it. Or maybe some other sort of fuckery going on with it.
What follows are just my own posts on Apple’s dev forum from today as I figured out what the fuck was going on with this bug, for the benefit of present and future search automatons②.
More Extended Blather
Last time, what made the bug heinous was how hard it was to debug and find out which esoteric implementation detail of my Xcode project was triggering the issue. I couldn’t reproduce the issue in some of my projects, but could in several others.
It turned out that some unknown thing Xcode does at launch time determined whether or not the bug would occur during that launch. The next time Xcode launched, it would again randomly determine whether or not the bug would occur. So to test any change you made, and see if that change helped prevent the bug from occurring, you had to quit and relaunch Xcode TEN MOTHERFUCKING TIMES. (And of course, by the time you figured this out, you would have already been fucking with it for some hours.)
It thus took me several hours to figure out that we could easily work around that bug by making sure that the project name did not contain the name of any command line tool that the project built. (So if your project builds a tool called “DiskEncrypter”, you just have to make sure that the name of your project file is “Disk_FUCKYOUXCODE_Encrypter” or similar, and you’re safe. Simple to work around; quite hard to find the workaround.)
Today I met another Xcode bug that completely broke a key IDE feature: editing the .xib files that define the UI for modern Mac applications. Once again, the underlying trigger is simple; once you know what to look for, you can work around the bug easily. But if you don’t know what to look for, figuring it out will cost you about a day.
The main reason I write posts like this one is so that future people suffering from the same problem as me can just google the answer and save themselves the horror of wasting hours on pointless fuckery like I had to do. (And, obviously, to indulge in some cathartic ranting.)
This time, I’m too tired to embellish the bug report with a gripping human drama about magical fairies, sexism, and recreational drug use, so below, I’m just going to paste the series of my own comments from the thread I started on Apples (lame, slow, closed, un-googleable, etc, sigh) developer forum.
(Like most things Apple, that thread exists in a closed, walled garden that you may only access with Apple’s permission and in ways they approve of: https://devforums.apple.com/message/605066)
How to manually force IB to reload a class definition
IB’s automagic reloading of class files doesn’t work, and it wastes a lot of my time. I hope there is a manual way.
Suppose I have FooViewController as the owner of FooView.xib. I add an IBOutlet to FooViewController.h, but it doesn’t show up in IB. IB’s notion of the class is generally some previous version that does not reflect the current contents of the class header.
This happens almost every day, and the only way I know to fix the situation is to quit and relaunch Xcode, which takes a long time. I often have to quit and relaunch Xcode each time I add any type of outlet to a view controller. This means quitting and relaunching Xcode dozens of times per day.
Is there any trick to force IB to re-examine a class’s header? I know it is supposed to work magically, but it doesn’t (and has never worked reliably on any of my machines on any Xcode 4 release; I am using 4.2.1 currently).
I see this behavior even for new classes, with no cruft in the header.
I notice, though, that if I select the File’s Owner and then show the Connections inspector (View→Utilities→Show Connections Inspector), below the incorrect list of outlets in the “Outlets” section there is a small spinning progress indicator labeled “Updating…”. It spins forever… the updating never finishes, and the newly added outlets don’t appear in the .xib editor until I quit and relaunch Xcode. So the spinner never gets done, for me.
Sigh. Filed as bug #10698256.
Thanks for the suggestion. That didn’t actually solve, but it is a clue. And, although the bug hits seemingly at random, I’ve now managed to create a test project that seems to reliably trigger the bug within a minute or two of adding outlets to a .xib’s File Owner object.
After nuking the Derived Data dir manually, I notice that Xcode’s car-dashboard display gets stuck saying something like “Scanning Classes | Processed 151 of 1072 files”. Once in this borked state, .xib editing no longer works. The exact number where it stops changes from launch to launch. A screen shot is below — Xcode never gets past this stuck state. If I quit and relaunch, it will get stuck again at a different point, but the outlets added the previous launch will show up in the .xib editor.
So it seems like you are suggesting that if Xcode cannot generate the project index — or perhaps if Xcode corrupts the index — the .xib editing features might stop working as I’ve described. And that seems to be corroborated by my experience.
If so, for me the next step would be try to figure out why Xcode cannot index my project. I have no idea — nothing strikes me as too unusual about it. But nib editing does work in a trivial project (the default Cocoa.app, adding outlets to the nib) and doesn’t work (as described in my bug report) in any of my real projects. What’s the difference? All my real projects:
use git submodules. So some of the code being indexed is in repo A, and some is in repo B which is a git submodule of A.(I tested this theory by creating a non-version-controlled copy of the demo project; bug still hit.)use .xcconfig files, which inherit from a base .xcconfig file.
??? (Any suggestions of things that might affect Xcode’s indexing process would be very welcome.)
I guess I can also attach my bug demo project to the bug report, for ease of reproduction, though I’ll have to get approval to share the source code with Apple.
Searching the web, I find a tweet from Steve Streeting (whose fucking awesome work coincidentally happens to be showcased in the first screenshot in this blog post): “I’ll make you a deal Xcode: if you don’t leap into ‘Scanning Classes X of 1670 files’ again today, I won’t punch you in the face.”
Also, one solitary Stack Overflow post, wherein the author had the same problem (Xcode getting borked in the ‘Scanning Classes’ phase), and then answered his own question. The cause was a malformed header file that was choking Xcode. Unfortunately, he said he basically had to use trial and error to find the header file.
So maybe there is a file or files in my source tree causing this problem. Probably a file used in most of my projects, that being the reason I tend to see this bug all the time in various projects.
I checked what files Xcode has open right now, in its borked state, but unfortunately no clue there…
1/17/12 11:33:11.169 AM Xcode: _AMDDeviceAttachedCallbackv3 (thread 0x104f9e000): Device 'AMDevice 0x400315900 {UDID = 0f00d513253e35c985daffd8f3663a1b1939dceb, device ID = 11, location ID = 0xfa410000, product ID = 0x129f}' attached.
1/17/12 11:33:12.210 AM Xcode: _AMDDeviceAttachedCallbackv3 (thread 0x109439000): Device 'AMDevice 0x40139ac20 {UDID = 0f00d513253e35c985daffd8f3663a1b1939dceb, device ID = 11, location ID = 0xfa410000, product ID = 0x129f}' attached.
1/17/12 11:33:12.275 AM Xcode: Unable to find either a loadable database or a Nodes.xml configuration file
1/17/12 11:33:12.277 AM Xcode: Error loading docSet file://localhost/Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.DeveloperTools.docset/: Error Domain=com.apple.DADocSetAccess Code=12 "Documentation set could not be read." UserInfo=0x4001db180 {NSLocalizedDescription=Documentation set could not be read., NSLocalizedFailureReason=Unable to find the database or 'Nodes.xml' configuration file.}
1/17/12 11:33:12.552 AM Xcode: _GCDSearchPthreadOnceRoutine (thread 0x109439000): Looked up GCD symbols, got addresses: 8e42f884, GCD_AVAILABLE is 1
1/17/12 11:33:12.665 AM Xcode: <DVTSplitView: 0x4019f9280>: the delegate <DVTSplitView: 0x4019f9280> was sent -splitView:resizeSubviewsWithOldSize: and left the subview frames in an inconsistent state:
1/17/12 11:33:12.665 AM Xcode: Split view bounds: ((0, 0), (600, 325))
1/17/12 11:33:12.665 AM Xcode: Subview frame: ((0, 0), (260, 500))
1/17/12 11:33:12.665 AM Xcode: Subview frame: ((261, 0), (378, 500))
1/17/12 11:33:12.665 AM Xcode: Subview frame: ((640, 0), (260, 500))
1/17/12 11:33:12.665 AM Xcode: The outer edges of the subview frames are supposed to line up with the split view's bounds' edges. NSSplitView is working around the problem, perhaps at the cost of more redrawing. (This message is only logged once per NSSplitView.)
1/17/12 11:33:13.101 AM Xcode: The class 'IBLibraryObjectTemplate' overrides the method identifier. This method is implemented by class 'NSView'
1/17/12 11:33:13.101 AM Xcode: The class 'IBLibraryObjectTemplate' overrides the method setIdentifier:. This method is implemented by class 'NSView'
1/17/12 11:33:14.129 AM mds: (Error) Task: Can't init <MDSClient 0x7fbf94c09a50 shutdown:NO got shutdown notification:NO> -- too many objects for MDSTaskRegistry
1/17/12 11:33:14.130 AM Xcode: BUG in libdispatch: 11C74 - 1819 - 0x4
The above is what Xcode logs when opening the project and getting into its borked state.
~~~
Interesting… I just noticed that after adding an outlet to my view controller, it did not appear in the .xib editor, per the bug. But after about 30 minutes of waiting, it did finally show up. So it isn’t hanging infinitely, it is just hanging for 30 minutes or so. That made me wonder what Xcode is doing when it is in this borked state, since it seems not to actually be permanently hanging the process. I omit the irrelevant-looking threads in the sample below, but these are similar to two more samples five mintes apart, and looks like something is just making the indexing process take an extreeeeeeemely long time, rather than failing completely:
2035 Thread_15942608 DispatchQueue_1251: com.apple.dt.Xcode.IBHeaderScanningClassProvider (serial)
+ 2035 start_wqthread (in libsystem_c.dylib) + 13 [0x7fff84fdcb85]
+ 2035 _pthread_wqthread (in libsystem_c.dylib) + 316 [0x7fff84fdb3da]
+ 2035 _dispatch_worker_thread2 (in libdispatch.dylib) + 198 [0x7fff8e42c760]
+ 2035 _dispatch_queue_invoke (in libdispatch.dylib) + 54 [0x7fff8e42cf66]
+ 2035 _dispatch_queue_drain (in libdispatch.dylib) + 264 [0x7fff8e42d10a]
+ 2035 _dispatch_call_block_and_release (in libdispatch.dylib) + 18 [0x7fff8e42b8ba]
+ 2035 __block_global_5 (in IDEInterfaceBuilderKit) + 630 [0x10542fa41]
+ 2035 -[IBSourceCodeParser parseFile:errors:] (in IDEInterfaceBuilderKit) + 136 [0x10537db6a]
+ 2035 -[IBObjcParser parseData:fromFile:errors:] (in IDEInterfaceBuilderKit) + 165 [0x10537d821]
+ 2035 nextRealToken (in IDEInterfaceBuilderKit) + 83 [0x10537c9f6]
+ 2035 IBObjCLexerNextToken (in IDEInterfaceBuilderKit) + 31 [0x10537a4bb]
+ 909 skipSpacesAndCR (in IDEInterfaceBuilderKit) + 141 [0x10537b7a1]
+ ! 909 parseGetc (in IDEInterfaceBuilderKit) + 18,67,... [0x10537b81f,0x10537b850,...]
+ 724 skipSpacesAndCR (in IDEInterfaceBuilderKit) + 141,193,... [0x10537b7a1,0x10537b7d5,...]
+ 402 skipSpacesAndCR (in IDEInterfaceBuilderKit) + 171 [0x10537b7bf]
+ 402 parseGetc (in IDEInterfaceBuilderKit) + 11,0,... [0x10537b818,0x10537b80d,...]
2035 Thread_15948863 DispatchQueue_2425: com.apple.dt.Xcode.IBHeaderScanningClassProvider (serial)
+ 2035 start_wqthread (in libsystem_c.dylib) + 13 [0x7fff84fdcb85]
+ 2035 _pthread_wqthread (in libsystem_c.dylib) + 316 [0x7fff84fdb3da]
+ 2035 _dispatch_worker_thread2 (in libdispatch.dylib) + 198 [0x7fff8e42c760]
+ 2035 _dispatch_queue_invoke (in libdispatch.dylib) + 54 [0x7fff8e42cf66]
+ 2035 _dispatch_queue_drain (in libdispatch.dylib) + 264 [0x7fff8e42d10a]
+ 2035 _dispatch_call_block_and_release (in libdispatch.dylib) + 18 [0x7fff8e42b8ba]
+ 2035 __block_global_5 (in IDEInterfaceBuilderKit) + 630 [0x10542fa41]
+ 2035 -[IBSourceCodeParser parseFile:errors:] (in IDEInterfaceBuilderKit) + 136 [0x10537db6a]
+ 2035 -[IBObjcParser parseData:fromFile:errors:] (in IDEInterfaceBuilderKit) + 165 [0x10537d821]
+ 2035 nextRealToken (in IDEInterfaceBuilderKit) + 83 [0x10537c9f6]
+ 2035 IBObjCLexerNextToken (in IDEInterfaceBuilderKit) + 31 [0x10537a4bb]
+ 905 skipSpacesAndCR (in IDEInterfaceBuilderKit) + 141 [0x10537b7a1]
+ ! 905 parseGetc (in IDEInterfaceBuilderKit) + 18,67,... [0x10537b81f,0x10537b850,...]
+ 749 skipSpacesAndCR (in IDEInterfaceBuilderKit) + 157,141,... [0x10537b7b1,0x10537b7a1,...]
+ 381 skipSpacesAndCR (in IDEInterfaceBuilderKit) + 171 [0x10537b7bf]
+ 381 parseGetc (in IDEInterfaceBuilderKit) + 0,28,... [0x10537b80d,0x10537b829,...]
2035 Thread_15950867 DispatchQueue_5158: com.apple.dt.Xcode.IBHeaderScanningClassProvider (serial)
+ 2035 start_wqthread (in libsystem_c.dylib) + 13 [0x7fff84fdcb85]
+ 2035 _pthread_wqthread (in libsystem_c.dylib) + 316 [0x7fff84fdb3da]
+ 2035 _dispatch_worker_thread2 (in libdispatch.dylib) + 198 [0x7fff8e42c760]
+ 2035 _dispatch_queue_invoke (in libdispatch.dylib) + 54 [0x7fff8e42cf66]
+ 2035 _dispatch_queue_drain (in libdispatch.dylib) + 264 [0x7fff8e42d10a]
+ 2035 _dispatch_call_block_and_release (in libdispatch.dylib) + 18 [0x7fff8e42b8ba]
+ 2035 __block_global_5 (in IDEInterfaceBuilderKit) + 630 [0x10542fa41]
+ 2035 -[IBSourceCodeParser parseFile:errors:] (in IDEInterfaceBuilderKit) + 136 [0x10537db6a]
+ 2035 -[IBObjcParser parseData:fromFile:errors:] (in IDEInterfaceBuilderKit) + 165 [0x10537d821]
+ 2035 nextRealToken (in IDEInterfaceBuilderKit) + 83 [0x10537c9f6]
+ 2035 IBObjCLexerNextToken (in IDEInterfaceBuilderKit) + 31 [0x10537a4bb]
+ 855 skipSpacesAndCR (in IDEInterfaceBuilderKit) + 141 [0x10537b7a1]
+ ! 855 parseGetc (in IDEInterfaceBuilderKit) + 18,67,... [0x10537b81f,0x10537b850,...]
+ 740 skipSpacesAndCR (in IDEInterfaceBuilderKit) + 155,141,... [0x10537b7af,0x10537b7a1,...]
+ 440 skipSpacesAndCR (in IDEInterfaceBuilderKit) + 171 [0x10537b7bf]
+ 440 parseGetc (in IDEInterfaceBuilderKit) + 0,67,... [0x10537b80d,0x10537b850,...]
Thanks for your responses. I will definitely file additional info in the bug report — I’m just sort of “live blogging” the bug here while I figure out what’s what.
My first priority is to work around this Xcode issue — because this is a show-stopping bug that is preventing me from getting the work of my actual job done, since I really can’t develop these apps if I have to reboot Xcode every time I modify a .xib. (I’m modernizing a set of legacy apps, with dozens of nibs, so re-creating all the UIs in code isn’t really feasible).
So posting here might actually garner some useful feedback that will help me find a workaround (and later I’ll write this up on my blog where it can be googled, unlike this forum). I’ve very rarely gotten any feedback from a bug report other than an autogenerated “Thanks for the report, this is a known issue, closed as Duplicate.”
Secondary to an immediate workaround, though, I’d love to see this fixed and so I will do what I can, including updating the bug with the best repro case I can come up with, samples, etc. But I really need Xcode to work ASAP — I assume Apple isn’t going to fix my bug release a new version of Xcode tomorrow.
Doing a binary search does indeed sound arduous but I don’t see any other choice, except maybe for reverting the .xib format on the files and using Xcode3 to edit .xibs.
So I will probably try it. What does make it especially arduous, and very similar to the previous major bug I discovered in Xcode in December, is that to see if the bug manifests or not takes a couple of minutes. You have to do the following process to see if a change you made causes the bug to disappear:
- Quit Xcode, then re-launch it.
- Open MyViewController.h and add an IBOutlet in code.
- Open the .xib and see if the outlet appears.
- Go back to the header and add an outlet.
- Go back to the .xib and see if the outlet appears.
- repeat steps 3 and 4 three more times.
- If an outlet added in code didn’t appear in the .xib editor, the bug has manifested. If all the outlets you added in code did appear, then the bug did not manifest.
So performing that process for every change I try is going to make a binary search take at least the rest of the day, unless I am very lucky…
Therefore, before the binary header-elimination search, I’m first going to try the ‘take a 20 minute nap while listening to rainstorm sounds and see if anything comes to you after you wake up’ debugging technique.
Whew. OK, I found the culprit. It was indeed a malformed header, from 2006, similar to the one reproduced below.
The bogus header doesn’t even have to be included in the Xcode project, it apparently just has to exist anywhere Xcode looks for headers. If such a header is present, the .xib editing features of Xcode 4.2.1 completely break. If you delete the header from disk, or fix the problem with it, things work again.
This particular header was in a work source tree that I use for almost all project — that’s why to me it seemed that Xcode4 was just totally broken, since this bug hit almost all my various projects.
For people encountering this bug, finding the malformed header might still be a challenge. Also I don’t know that this particular kind of syntax error — a typo-induced unterminated comment — is necessarily the only type of malformed header that will bork Xcode, but it was in my case. A header with nothing but comments in it was accidentally terminated with /*
instead of the proper closing */
.
Because the header was not part of the project, and wasn’t imported, so I never got any “unterminated comment” compile errors. This header would cause an error (or a warning, depending on how you roll with with your build settings) if it was actually included or imported by your project’s code.
So if you have this problem and need to fix it, I recommend that you start by looking for header files that are not part of your project, but exist in the source tree next to headers that your project does use.
I’ve updated the bug report ( rdar://10698256 ) to include a reproduction demo app, screenshot, and sample output, and I have also uploaded the bug demo project to github just in case anybody not working at Apple is interested:
As a final note, if you do hit the bug and then delete or fix the header file triggering it, you do indeed need to delete the Xcode “DerivedData” directory. Just deleting the header but leaving the (presumably corrupted) index data around will not fix the problem.
/*
THIS MALFORMED HEADER WILL COMPLETELY BREAK XCODE'S .XIB EDITING
This is a bug report demo app for Apple Problem ID: 10698256
If this header is included in your project the .xib editor will
stop seeing newly added outlets. For this demo app, follow these
steps:
1. Quit and re-launch Xcode.
2. Navigate to XcodeBugViewController.h and add an outlet, like
"IBOutlet NSView *foo1" and save the header file.
3. Navigate to XcodeBugViewController.xib and right click the "File's.
Owner" icon to pop up the list of outlets.
4. Note whether or not the outlet appears in the list.
5. Repeat steps 3 and 4 five times.
You should see that outlets are no longer showing up in Xcode.
If you delete this header, then quit and relaunch Xcode, the problem
will go away. (You have to literally delete the file from disk. It does
not need to be included in the project to trigger this bug.)
BONUS BUG: Editing this header within Xcode will cause it to crash or freeze.
/*
①: -ly annoying, at least.
②: I assert that this is the correct American spelling.