Xcode Search Gone Mad

December 13th, 2005

I ran into a search problem recently in one of my Xcode projects. Every time I conducted a particular search (in this case, the search term was “CFString”), my screen was littered with a series of cascading Xcode exception dialogs that look like this:

On the one hand, it’s great that Xcode goes out of its way to convey internal exceptions to the user. It means that they are less likely to just “let slide” exceptional conditions that would otherwise show up only in an archaeological dig through the system’s console log. On the other hand, when they spray dozens of them out at me while I’m just trying to search my project, it gets to be a real hassle to close them all after every search.

When I run into these kinds of errors from Xcode, I immediately start thinking of where my latest project backup is. It’s probably some kind of internal project format corruption. The last thing any of us wants to do is trash a project file and start from scratch, so it’s a good idea to keep backups of the project files (probably along with all your other sources, in the Subversion repository).

In this case I thought I’d be curious and try to get to the bottom of the problem. Since each of these dialogs corresponds to a raised NSException inside Xcode, I thought I’d attach with gdb and see what’s going on at the point of exception. This is what the top of the stack looks like while one of the annoying dialogs is being generated:

#0  0x928f9508 in -[NSException raise] ()
#1  0x95464098 in -[TSException raise] ()
#2  0x928f935c in +[NSException raise:format:] ()
#3  0x929c1794 in mutateError ()
#4  0x928e7a00 in -[NSCFString replaceCharactersInRange:withString:] ()
#5  0x928e7968 in -[NSConcreteMutableAttributedString replaceCharactersInRange:withString:] ()
#6  0x928f2a60 in -[NSConcreteMutableAttributedString initWithString:attributes:] ()
#7  0x98fc1004 in -[PBXFindResult initWithBookmark:textBookmarkResolvable:] ()
#8  0x98fc0e58 in -[PBXReferenceBasedBatchFinder reportBookmarks:findable:] ()
#9  0x98fbff3c in -[PBXTextBatchFinder doSomeFinding] ()
#10 0x98fbfd08 in +[PBXBatchFinder _backgroundBatchFinderNotification:] ()

Something to do with bookmarks! Well, I don’t even have any bookmarks in my project. Did I screw something one of those times I hit Cmd-D instead of Cmd-Shift-D to “Open Quickly?” I decide to set a breakpoint on the call to initWithBookmark:textBookmarkResolvable:, along with some exploratory breakpoint commands. I asked gdb to report to me what the bookmark argument looks like (“po” is short for “print-object”, useful for displaying Cocoa objects). I left my “raise” breakpoint, set to automatically continue, so I can tell what kind of circumstances immediately precede it. This is a “trap the thief” technique for pinpointing the misbehaving code. I let Xcode run wild and then look at the logged breakpoint information for clues as to what led to the assertion.

(gdb) b initWithBookmark:textBookmarkResolvable:
Breakpoint 15 at 0x98fc0ed8
(gdb) command 15
Type commands for when breakpoint 15 is hit, one per line.
End with a line saying just "end".
>po $r5
>c
>end

With the breakpoints in place, I return to Xcode and invoke my troublesome search. Looking back at the logged output in gdb, I see the following snippet (newlines added for readability):

Breakpoint 15, 0x98fc0ed8 in -[PBXFindResult initWithBookmark:textBookmarkResolvable:] ()
<PBXTextBookmark:0x08133700:rtn=2:fileRef=
<PBXFileReference:0x05e08d80:rtn=61:65562A49085CB7C5007AD50E:name='HackCFSocketStream.h'>
:timestamp=0:char-range={5485,8}:line-range={5485,8}:<range-type-0>={5485,8}>

Breakpoint 14, 0x928f9508 in -[NSException raise] ()

So, Xcode doesn’t like “HackCFSocketStream.h”? Aside from its obviously questionable name, what has it done so wrong? I take a look at my search results and sure enough, no HackCFSocketStream.h. Not only are the dialogs I’m getting annoying, they represent missing search results in Xcode!

I decide to try to narrow the problem down by doing a limited search on only the “bad boy.” I select the HackCFSocketStream.h file in Xcode’s files and groups tree, and do another search, this time selecting the “In Selected Project Items” scope-limiting option.

Son of a gun! No error dialog. It must be particular to doing a global search. I switch back to “In Project” and search again. No more bug. Gah! I seem to have “fixed” the bug by asking Xcode to limit its search scope to only the problematic file. I tried a few more problematic files, repeating the same identification method with gdb. Same results! Explicitly searching a selected file fixes the bug, apparently forever.

I tried to mix up the procedure a little bit. Instead of selecting the problematic file itself, I select the group that contains it. The bug still exists at this scope, but again, as soon as I select only the problematic file and search, the situation is resolved.

What I’m left with is a situation where I have a painstaking, yet effective method of ridding the project of this bug. Unfortunately, I have to wait until I encounter a search term that finds a problematic file. Then I have to identify the file and go in and do the magic cleansing search.

I suppose I might try to write a script or something that goes through and explicitly searches each file in the project. Will that clean things up? I should probably just revert to a backup project and hope for the best.

Update – More data points:

  • It appears that it’s not searching the “problem file” but actually just viewing it in the Xcode editor that causes it to be “fixed.” I guess Xcode is probably reindexing or something when this happens. Rebuilding the index for the entire project does not fix the problem. So I guess I could work around this by writing a script to go through and open an editor window for every file in my project. We’ll see.
  • A “problem file” is only problematic for a particular search term (or set of search terms, perhaps). For instance, a search of “text” in my source base causes a failure on a given file, but searching for another term present in that file brings it up just fine.

Update 2: The “fix” achieved by selecting an item is not permanent. It turns out that the error occurs only if the “bad file” is not currently open in Xcode. By selecting the file, it becomes open and stays open until explicitly closed. By explicitly closing the file (Cmd-Shift-W), I am able to reproduce the error again.

Update 3: Case Closed!

My curiosity wouldn’t let me leave this alone, and I finally tracked down the root cause of the problem. I have produced a very simple test project that exhibits the problem. If you’re curious, you can download the project here. Look at the single source file “TestSource.cp” for instructions on how to reproduce. I know enough about this problem now to summarize it in fairly brief form:

Xcode produces incorrect search result range for terms in closed files preceded by multibyte UTF8 characters.

I tried to get it shorter than that, but I can’t! So what’s the bottom line? If your source file contains a multi-byte UTF8 character, Xcode mistakenly tallies the bytes instead of the characters when computing the offset into that line. In most cases, this just means that the contextual highlighting of the search result is slightly off, but if the “overshoot” caused by these multi-byte characters causes a search result range to exceed the end of the line, then it causes the very unsavory runtime exception I’ve described in this article.

This is better demonstrated by example. For instance, in the test project I’ve linked to, the UTF8 encoded source file contains a line like this:

// •DCJ• Bullets are dangerous.

Each of those bullets surrounding my initials is encoded in the file by the UTF8 bytes “e2 80 a2”. So what happens if I do a search for “Bullets” in this project? If the file is open, I get the expected result:

But if the file is closed, Xcode is not so savvy and interprets the file apparently on a “byte == character” basis:

See how the emboldened portion of the result is exactly 4 characters “too far?” That’s because the two bullets take 6 bytes, and Xcode assumes that two bullets should take only 2 bytes. The difference is the “slop” that gets inappropriately added into the range.

When the slop in the range takes you beyond the scope of the line, you get an exception. If I search the project for “danger”, I observe the edge case, beyond which no further range can be safely returned for the given line:

Searching for “dangerous” is just that. You’ll end up witnessing one of Xcode’s friendly error dialogs.

In summary: any UTF multi-byte character in your source file steals “safely searchable” characters from the end of the line. If any search in your project yields results that are at the ends of lines with multibyte characters, you will likely witness an exception and fail to see your search in the resulting pane.

Radar 4377633.

Workaround: Save your source files as some other encoding than UTF8. I don’t really know which encoding is best. I guess I need to figure this out. For this particular project, which had become a mish-mash of UTF8 and MacRoman encoded files, I decided to switch all the UTF8 files back to MacRoman. I won’t be able to initialize UTF8 strings with non-MacRoman typed constant values, but at least I’ll be able to search my project in peace!

Helping Myself and Others

December 8th, 2005

The main reasons I write in this blog are for the fame and the glory. These will come some day, won’t they? Running a close third is the fact that, as I accumulate entries in this repository, the articles become part of the “Internet Reference Book,” upon which we all rely for “instant and easy answers” to the problems that vex us.

I recently installed a piece of software on my blog that records the “referring site” for people who end up clicking their way onto my blog through another site’s page. It’s been illuminating to discover just how many of these visitors are sent by way of Google or other search engines. In these cases, the particular query the user entered is contained in the referral listing. It’s fun, though a bit disconcerting to see that sometimes people other than myself type “daniel jalkut” into search engines. I also get a lot of false hits – folks I feel sorry for. They were looking for “dog sweater.com” and AOL decided that this entry was the #1 hit. Rude awakening! I also gets lots of knitting hits, and the occasional Perl or UNIX nerd whose query happens to match something I wrote in my mostly Mac OS X-centric posts.

It’s really gratifying to see searches come in where I just know this was the pot of gold they were looking for. One of my most popular posts is the “Automatic Build Sub-Versioning in Xcode” entry. I get about 2-3 search referrals per day for this page, with queries like “subversion automatic build number” or “xcode subversion script.” This is very gratifying because I’ve often been that person searching Google, praying for an easy answer. I like to imagine the person stumbling upon my site and thinking, “Yes! Now I have time to watch Letterman tonight!”

By putting “gems” in my blog as I discover them or become aware of their relative uniqueness, I am also building an archive for my own future reference. If I’m at a client’s office and don’t remember the details of some particularly brilliant tip I posted to the blog, I can just search my own pages and come up with it instantly. Over time these answer boxes serve not only the internet at large, but us forgetful authors as well!

Today I was looking at some enhancements for my FastScripts utility, and I realized I could accomplish something very cool – if only I could get notifications from the system whenever the frontmost application changed. Hmph. No NSApplication or NSWorkspace notifications for this. What am I going to do? I searched the list archives at CocoaBuilder.com for “front process changed.”

A few items down the list of search results, I see an item called “Cheezy Hack To Get ‘Front App Changed’ Events.” OK, so I’m not the first person to run into this. Maybe this will help me out. Wait a minute. I’m the author of the post! It turns out that over a year ago, I figured this problem out. Before I had a blog to put bits of wisdom like this into, I would occasionally post them on lists like Cocoa-Dev.

This post is exactly the type of entry that might end up as a blog entry nowadays. A hard-won bit of wisdom that I want others to benefit from without having to work quite as hard. Thanks to Google and Cocoabuilder.com, it’s part of the Internet Reference Book, even though it predates my blog.

I took a look at my post. It started to ring a bell. I grabbed some of the sample code out of the write-up and searched my FastScripts project for it. What do you know? I’m already generating “front application changed” notifications in the application. I had to add this functionality to make application-specific keyboard shortcuts work. Doy hickey! All I have to do to leverage this into the new part of my application is add an observer for the NSNotification.

It’s amazing that I forgot this so quickly, but a powerful testament to the internet and the concept of personal “idea archives” that I was able to recover the solution so quickly.

Got a good idea? Write it down! Don’t write it down just anywhere – write it down in a Google-indexed web page. You’ll be grateful you did. And so will we!

Center Windows in Interface Builder

December 6th, 2005

How many times have you been bitten by the problem of moving a window in Interface Builder, for easier editing, and then neglecting to put it back before saving it? Sometimes this can pop up as an embarrassing bug when you realize that your preferences have been saving an appropriate location for the window, but now new users are seeing it pop-up in the lower left corner of the window. Where you left it the last time you edited it in Interface Builder!

One of the ingredients leading to this disaster is the relative difficulty of getting the window back to precisely where it belongs. Many times, a window is saved “centered” with the window autoposition springs set so that it will be centered on whatever screen the user has. But the tedium of getting it “back to center” can make you hesitant to move it to a more convenient position for editing. Worse, it can make it easy to “postpone” the act of putting it back where it belongs until you forget about it and ship that way.

Unfortunately, the amazingly useful alignment and centering tools provided in Interface Builder only work on view-level interface elements. They don’t work on windows. Wouldn’t it be nice if you could easily and quickly snap a window back to a centered position?

My Center Window script lets you do just that. By adding this to the Interface Builder scripts menu, you can easily bounce a window back to horizontal center at any time by selecting the script from the Apple Script Menu, or (better!) by pressing a FastScripts keyboard shortcut. I’m using Cmd-Opt-C to center windows in the blink of an eye.

Note: This script requires the Jon’s Commands scripting addition. If you don’t want to install that, you can modify the script to define the width and height of your main screen manually.

It should be pretty easy to modify the script to also position the Y-axis as you see fit.

Throttling NSSlider

December 5th, 2005

Suppose you want to throttle the rate at which a continuous NSSlider sends its action. To save you from the same fate I met, I will talk you through some of the avenues you might pursue, and ultimately describe the simple solution I adopted which is both easiest and is the only solution I know of that actually gets the job done.

Searching the documentation, you might discover the very intriguing “getPeriodicDelay:interval:” method of NSCell. The description of this method in the Apple documentation is as follows:

Returns initial delay and repeat values for continuous sending of action messages to target objects.

Sounds like a winner! Sigh. It’s not.

The problem, as I’ve deduced after several hours of beating my head against a wall, wondering whether I am in fact not cut out for a programming career after all, is that “continuous” means two distinct things, depending on the type of NSCell you are dealing with. The documentation rarely addresses this, and even misleads us with documentation like this where outright promises are made about the relationship between “continuous actions” and the periodic delay attribute.

The two categories of continuous controls in Cocoa are “periodic” and “send on drag”. Hints at this distinction in the documentation are few and far between, but if you look in the description of NSCell’s “sendActionOn:” method, you’ll see this line (emphasis mine):

You can use setContinuous: to turn on the flag corresponding to NSPeriodicMask or NSLeftMouseDraggedMask, whichever is appropriate to the given subclass of NSCell.

What does it mean by this? It means that controls whose cells are continuous in a user-driven nature, like a user tweaking a knob or pushing a slider, shouldn’t send their value continuously when the user is not … uh, tweaking. Controls like NSButton, which are simply pressed as the user tracks them, need some stimulation for the periodic send: a periodic timer. Controls like NSSlider can rely on the user to move the mouse when the action needs to be sent.

Unfortunately, what I’ve discovered is that the timing of “send on drag” controls is completely un-throttled. The control’s cell appears to send out actions as fast as the user can generate drag events. If drag events were only generated every pixel or whatever, that would be fine (* See End of Entry), but drag events come pouring through your application as the user makes the slightest nudge of their pointing device. Depending on what you’re doing in your slider response, this might be highly undesirable.

Ideally, I would expect that “send on drag” cells should also limit their sending to the interval identified by the getPeriodicDelay:interval: method. “Send on drag” cells should be an optimization of “periodic” cells, not a completely new implementation. This would make all of Apple’s documentation correct. As it is, most of Apple’s documentation having to do with “continuous controls and periodic events” is pretty misleading if not downright incorrect.

Working Around the Problem

It turns out that this problem can be worked around in a pretty simple way that doesn’t even require subclassing the associated control cell. It’s too bad that the documentation was confusing and nothing worked like it said it would(*), but now that I’ve come to this solution, I’m happy it’s the simplest of them all.

Easy Solution: Throttle continuous “send on drag” controls by overriding “sendAction:to:”.

Instead of limiting the frequency of the “send” action, just nip it in the bud. Whenever the cell for your control ultimately decides it’s ready to send an action, it goes through the sendAction:to: method. By subclassing the control and placing a date throttle on the method, you can achieve whatever throttling you feel is appropriate.

Note: Several readers noticed a shortcoming in the original version: the final value was often not sent because the throttling prevented it from being sent when the “expiration date” had not yet arrived. Special thanks to Jack Nutting for observing that a quick check of the current event type would solve the problem nicely. The only reservation I have about this is whether there are automated ways of manipulating a slider that might not involve mouse ups and downs. Does anybody know if this is possible?

The code below has been updated to include a check for this “final value” condition. Let me know if anybody spots other problems that should be remedied!

	static NSTimeInterval kTimeBetweenSends = 0.2;	// 1/5 second

	- (BOOL)sendAction:(SEL)theAction to:(id)theTarget
	{
		BOOL handleEvent = NO;
		
		// Always send action when the mouse comes up. This means that the final 
		// send is probably coming through, which we don't want to miss!
		if ([[NSApp currentEvent] type] == NSLeftMouseUp)
		{
			handleEvent = YES;
			
			// We also want to clear the "next time" so 
			// we start fresh if the user quickly clicks again
			[mNextSendTime release];
			mNextSendTime = nil;
		}
		else 
		{	
			// Only pass actions on if we're not waiting for a throttling date to pass
			NSDate* nowDate = [NSDate date];
			if ((mNextSendTime == nil) || ([mNextSendTime compare:nowDate] == NSOrderedAscending))
			{		
				handleEvent = YES;
			}
		}
		
		if (handleEvent == YES)
		{
			handleEvent = [super sendAction:theAction to:theTarget];

			[mNextSendTime release];
			mNextSendTime = [[NSDate dateWithTimeIntervalSinceNow:kTimeBetweenSends] retain];
		}
		
		return handleEvent;
	}

So if you find yourself in my position someday, my advice is this: ignore Apple’s documentation on this issue and implement something like the above instead. Easier to subclass, easier to inject, and it actually works.

Update: If you’re inside Apple and are wondering whether I wrote a bug or not, here it is: 4365583.

* Update 2: When I said it would be “OK” if it only sent the value when the pixel changes, I was wrong. It’s not OK. In fact, that’s what it does. Kudos to Apple! But… it’s still not OK. I don’t want the changes as fast as the user can move the mouse. That’s too fast! My need for this subclass still stands, though the argument about the pixel-based notifications is null.