Local variables are free

December 19th, 2009

This is part II of my irregularly scheduled series on compiler optimization. In part I, I explained how the compiler can optimize away return statements, resulting in missed breakpoints. My given workaround to that problem, though effective, was very ugly and architecture-dependent, much like Cowboys Stadium.

(gdb) break *0x00001fc5 if $eax != 0

Although there’s not much we can do to prevent the compiler optimization, we can greatly simplify our conditional breakpoint. I had suggested rewriting the source code, which was awe-inspiringly prescient, because that’s what I’m going to do now. Here’s the original code:

8	if (ShouldReturn())
9		return;

And here’s the revised code:

8	int localVar = ShouldReturn();
9	if (localVar)
10		return;

The return at line 10 will still be optimized away. However, the revised code allows us to set a simple breakpoint at line 9 that will stop when we want:

(gdb) break 9 if localVar != 0

No knowledge of the architecture, machine registers, or assembly language is required.

From the beginning of time (January 1970, of course), programmers have struggled over coding style. Objective-C programmers, for example, expend undue effort arranging their brackets. (I have [NSMutableArray array] going to the Final Four.) For some, bracket-making becomes a kind of game or contest.

[[[[[[[[[[[[[See how] many] method] calls] we] can] fit] on] one] line] of] source] code];

I’ve changed my coding style over the years, but I’ve settled on one fundamental principle: write your code so that it’s easy to debug. All your fancy margin-aligning isn’t going to help when you need to figure out why your app keeps exploding. If you have nested method calls on one line of code, you can’t easily set a breakpoint in the middle. That’s why I prefer as much as possible to have only one method call per line of code, and create a local variable to store the return value.

There is a misconception that local variables are expensive, in terms of either computation or memory. The truth is that local variables are very cheap, the value meals of the computing world. (Would you like trans fat with your saturated fat?) It only takes one machine instruction to store a pointer address to a local variable. One machine instruction is really quite fast, about as fast as you can get — at least with restrictor plates. With regard to memory, local variables only take up stack space. To create a local variable, you simply move the stack a little. When the method or function returns, the stack is moved back, and thereby the space reserved for local variables is automatically recovered. Of course, you don’t want to create large C arrays on the stack, but a pointer to an Objective-C object only takes 4 bytes on the stack for 32-bit, 8 bytes for 64-bit. The default 32-bit stack size is 8MB, so you’re not going to run out of space unless you have deeply recursive calls.

Even these small costs are only relevant in the context of your app’s unoptimized, debug configuration. For your customers, on the other hand, local variables are free. As in Mumia, or Bird. When you compile your app using the release configuration, the local variables disappear, the compiler optimizes them away. (By the way, this is one of the reasons that debugging the release build of your app can be a frustrating and/or wacky experience.) To see the optimization in action, let’s consider some sample code:

1  #import <Foundation/Foundation.h>
2
3  @interface MyObject : NSObject {}
4  @end
5
6  @implementation MyObject
7
8  -(NSString *)myDirectProcessName {
9  	return [[[NSProcessInfo processInfo] processName] lowercaseString];
10 }
11
12 -(NSString *)myRoundaboutProcessName {
13 	NSString *myRoundaboutProcessName = nil;
14 	NSProcessInfo *processInfo = [NSProcessInfo processInfo];
15 	NSString *processName = [processInfo processName];
16 	NSString *lowercaseString = [processName lowercaseString];
17 	myRoundaboutProcessName = lowercaseString;
18 	return myRoundaboutProcessName;
19 }
20
21 @end
22
23 int main(int argc, const char *argv[]) {
24 	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
25 	MyObject *myObject = [[[MyObject alloc] init] autorelease];
26 	NSLog(@"My direct process name: %@", [myObject myDirectProcessName]);
27 	NSLog(@"My roundabout process name: %@", [myObject myRoundaboutProcessName]);
28 	[pool release];
29 	return 0;
30 }

The above code is obviously contrived and useless. It only has value for explanatory purposes, and perhaps in the app store for $0.99. The methods -myRoundaboutProcessName and -myDirectProcessName do the same thing, the former with and the latter without local variables. Here’s the i386 disassembly for the methods when compiled using the debug configuration:

-[MyObject myDirectProcessName]:
00001d2a	nop
00001d2b	nop
00001d2c	nop
00001d2d	nop
00001d2e	nop
00001d2f	nop
00001d30	pushl	%ebp
00001d31	movl	%esp,%ebp
00001d33	pushl	%ebx
00001d34	subl	$0x14,%esp
00001d37	calll	0x00001d3c
00001d3c	popl	%ebx
00001d3d	leal	0x000012e8(%ebx),%eax
00001d43	movl	(%eax),%eax
00001d45	movl	%eax,%edx
00001d47	leal	0x000012e4(%ebx),%eax
00001d4d	movl	(%eax),%eax
00001d4f	movl	%eax,0x04(%esp)
00001d53	movl	%edx,(%esp)
00001d56	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001d5b	movl	%eax,%edx
00001d5d	leal	0x000012e0(%ebx),%eax
00001d63	movl	(%eax),%eax
00001d65	movl	%eax,0x04(%esp)
00001d69	movl	%edx,(%esp)
00001d6c	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001d71	movl	%eax,%edx
00001d73	leal	0x000012dc(%ebx),%eax
00001d79	movl	(%eax),%eax
00001d7b	movl	%eax,0x04(%esp)
00001d7f	movl	%edx,(%esp)
00001d82	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001d87	addl	$0x14,%esp
00001d8a	popl	%ebx
00001d8b	leave
00001d8c	ret
-[MyObject myRoundaboutProcessName]:
00001d8d	nop
00001d8e	nop
00001d8f	nop
00001d90	nop
00001d91	nop
00001d92	nop
00001d93	pushl	%ebp
00001d94	movl	%esp,%ebp
00001d96	pushl	%ebx
00001d97	subl	$0x24,%esp
00001d9a	calll	0x00001d9f
00001d9f	popl	%ebx
00001da0	movl	$0x00000000,0xe8(%ebp)
00001da7	leal	0x00001285(%ebx),%eax
00001dad	movl	(%eax),%eax
00001daf	movl	%eax,%edx
00001db1	leal	0x00001281(%ebx),%eax
00001db7	movl	(%eax),%eax
00001db9	movl	%eax,0x04(%esp)
00001dbd	movl	%edx,(%esp)
00001dc0	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001dc5	movl	%eax,0xec(%ebp)
00001dc8	movl	0xec(%ebp),%edx
00001dcb	leal	0x0000127d(%ebx),%eax
00001dd1	movl	(%eax),%eax
00001dd3	movl	%eax,0x04(%esp)
00001dd7	movl	%edx,(%esp)
00001dda	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001ddf	movl	%eax,0xf0(%ebp)
00001de2	movl	0xf0(%ebp),%edx
00001de5	leal	0x00001279(%ebx),%eax
00001deb	movl	(%eax),%eax
00001ded	movl	%eax,0x04(%esp)
00001df1	movl	%edx,(%esp)
00001df4	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001df9	movl	%eax,0xf4(%ebp)
00001dfc	movl	0xf4(%ebp),%eax
00001dff	movl	%eax,0xe8(%ebp)
00001e02	movl	0xe8(%ebp),%eax
00001e05	addl	$0x24,%esp
00001e08	popl	%ebx
00001e09	leave
00001e0a	ret

As expected, -myRoundaboutProcessName makes more room on the stack than -myDirectProcessName:

00001d34	subl	$0x14,%esp
00001d97	subl	$0x24,%esp

At 00001da0, -myRoundaboutProcessName sets the value of the local variable to nil, as in line 13 of the source code. The interesting differences, though, are immediately after the calls to objc_msgSend(). By the standard ABI, the register eax contains the return value of objc_msgSend(). In -myDirectProcessName, the value in eax is simply moved to the register edx:

00001d5b	movl	%eax,%edx

In contrast, -myRoundaboutProcessName first stores the value on the stack before moving it to edx. The address on the stack is the space reserved for the local variable:

00001dc5	movl	%eax,0xec(%ebp)
00001dc8	movl	0xec(%ebp),%edx

After the final objc_msgSend() call, -myDirectProcessName doesn’t bother to do much, because the return value in eax will become the return value of the whole method. In -myRoundaboutProcessName, it needs to store values in local variables as in lines 16 and 17 of the source code:

00001df9	movl	%eax,0xf4(%ebp)
00001dfc	movl	0xf4(%ebp),%eax
00001dff	movl	%eax,0xe8(%ebp)
00001e02	movl	0xe8(%ebp),%eax

So that’s how the methods differ in the unoptimized build. Now let’s see what happens when we use the release configuration. Here’s the optimized disassembly for -myDirectProcessName:

-[MyObject myDirectProcessName]:
00001dce	pushl	%ebp
00001dcf	movl	%esp,%ebp
00001dd1	subl	$0x18,%esp
00001dd4	movl	0x00003000,%eax
00001dd9	movl	%eax,0x04(%esp)
00001ddd	movl	0x0000302c,%eax
00001de2	movl	%eax,(%esp)
00001de5	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001dea	movl	0x00003004,%edx
00001df0	movl	%edx,0x04(%esp)
00001df4	movl	%eax,(%esp)
00001df7	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001dfc	movl	0x00003008,%edx
00001e02	movl	%edx,0x0c(%ebp)
00001e05	movl	%eax,0x08(%ebp)
00001e08	leave
00001e09	jmpl	0x0000400a	; symbol stub for: _objc_msgSend

The optimized method is significantly shorter, as expected from the compiler option -Os. First, you’ll notice that all those pesky nop instructions have been deleted. Stallman put them in unoptimized builds just to annoy us. (Or they may have been for Fix and Continue, but I always assume the worst.) There are additional optimizations as well that I won’t belabor here, because I’m eager to get to the climax. (Sorry, dear.) For your enlightenment and enjoyment, here’s the optimized disassembly for -myRoundaboutProcessName:

-[MyObject myRoundaboutProcessName]:
00001e0e	pushl	%ebp
00001e0f	movl	%esp,%ebp
00001e11	subl	$0x18,%esp
00001e14	movl	0x00003000,%eax
00001e19	movl	%eax,0x04(%esp)
00001e1d	movl	0x0000302c,%eax
00001e22	movl	%eax,(%esp)
00001e25	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001e2a	movl	0x00003004,%edx
00001e30	movl	%edx,0x04(%esp)
00001e34	movl	%eax,(%esp)
00001e37	calll	0x0000400a	; symbol stub for: _objc_msgSend
00001e3c	movl	0x00003008,%edx
00001e42	movl	%edx,0x0c(%ebp)
00001e45	movl	%eax,0x08(%ebp)
00001e48	leave
00001e49	jmpl	0x0000400a	; symbol stub for: _objc_msgSend

Identical! Ah, that’s nice. Smoke ‘em if you got ‘em.

In conclusion, feel free to sprinkle, pepper, dash, or even drown your code with local variables. And with the engineering hours of debugging time you save, get me a nice (not free) present. I’m partial to flavored coffee and unflavored MacBooks.

Why did my breakpoint not get hit?

November 16th, 2009

This is part I of a II+ (take that, trademark trolls) part series on compiler optimization. For the gcc compiler, you can specify the level of optimization with various -O options. The default for compiling is -O0, which means do not optimize. As we shall see, however, the compiler always optimizes to an extent. That is to say, gcc -O0, you lie!

The primary reason for using the -O0 option (besides to avoid compiler optimization bugs) is to facilitate debugging of your code. With higher levels of optimization, the compiler is given more freedom to ‘ignore’ your source code in writing machine instructions, as long as the results are the same. Although it is possible to debug optimized binaries, the experience is often confusing and unhelpful for the programmer (much like reading cocoa-dev). Turning off optimization gives the closest correlation between source code and machines instructions. Yet even with no optimization, the correlation is not perfect, and this can lead to debugging problems.

Let’s consider a simple example:

$ cat > returnbreak.c
#include <stdio.h>

int ShouldReturn(void) {
	return 1;
}

void HelloWorld(void) {
	if (ShouldReturn())
		return;

	printf("Hello, World!\n");
}

int main(int argc, const char *argv[]) {
	HelloWorld();
	return 0;
}
$ gcc -g -O0 -o returnbreak returnbreak.c
$ gdb returnbreak
GNU gdb 6.3.50-20050815 (Apple version gdb-966) (Tue Mar 10 02:43:13 UTC 2009)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin"...Reading symbols for shared libraries ... done

(gdb) list HelloWorld
2
3	int ShouldReturn(void) {
4		return 1;
5	}
6
7	void HelloWorld(void) {
8		if (ShouldReturn())
9			return;
10
11		printf("Hello, World!\n");
(gdb) break 9
Breakpoint 1 at 0x1fc9: file returnbreak.c, line 9.
(gdb) run
Starting program: /Users/jeff/Desktop/returnbreak
Reading symbols for shared libraries ++. done

Program exited normally.

WTF?!? Why did my breakpoint not get hit?

(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x00001fc9 in HelloWorld at returnbreak.c:9

Hmm, that seems ok. Let’s try something else.

(gdb) break HelloWorld
Breakpoint 2 at 0x1fc0: file returnbreak.c, line 8.
(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x00001fc9 in HelloWorld at returnbreak.c:9
2   breakpoint     keep y   0x00001fc0 in HelloWorld at returnbreak.c:8
(gdb) run
Starting program: /Users/jeff/Desktop/returnbreak 

Breakpoint 2, HelloWorld () at returnbreak.c:8
8		if (ShouldReturn())
(gdb) c
Continuing.

Program exited normally.

Odd, it hits the breakpoint at line 8 but not at line 9. The breakpoint on line 9 is at address 0x00001fc9, so let’s look at the (i386) disassembly for that:

(gdb) disassemble 0x00001fc9
Dump of assembler code for function HelloWorld:
0x00001fb3 <HelloWorld+0>:	push   %ebp
0x00001fb4 <HelloWorld+1>:	mov    %esp,%ebp
0x00001fb6 <HelloWorld+3>:	push   %ebx
0x00001fb7 <HelloWorld+4>:	sub    $0x14,%esp
0x00001fba <HelloWorld+7>:	call   0x1fbf <HelloWorld+12>
0x00001fbf <HelloWorld+12>:	pop    %ebx
0x00001fc0 <HelloWorld+13>:	call   0x1fa6 <ShouldReturn>
0x00001fc5 <HelloWorld+18>:	test   %eax,%eax
0x00001fc7 <HelloWorld+20>:	jne    0x1fd7 <HelloWorld+36>
0x00001fc9 <HelloWorld+22>:	lea    0x30(%ebx),%eax
0x00001fcf <HelloWorld+28>:	mov    %eax,(%esp)
0x00001fd2 <HelloWorld+31>:	call   0x3005 <dyld_stub_puts>
0x00001fd7 <HelloWorld+36>:	add    $0x14,%esp
0x00001fda <HelloWorld+39>:	pop    %ebx
0x00001fdb <HelloWorld+40>:	leave
0x00001fdc <HelloWorld+41>:	ret
End of assembler dump.

When ShouldReturn() returns, the return value is in the register eax. The test instruction at 0x00001fc5 performs a bitwise AND of the two operands — which in this case are the same. If the result is non-zero — and in this case the result is 1 — the Zero Flag in the EFLAGS register is set to 0. This instruction corresponds to evaluating the conditional on line 8 of our source code. Then the jne instruction at 0x00001fc7 jumps to a certain address if the Zero Flag is 0. In our source code, the flow of control should move to the return statement on line 9 when the conditional evaluates to non-zero. According to the machine instructions, on the other hand, it jumps to 0x1fd7 when the conditional evaluates to non-zero. This address is the beginning of the standard function epilog, which restores the stack and registers to their previous state before returning.

The problem here is that while the function HelloWorld() has two exit points in our source code, it only has one exit point in the machine instructions. In essence, the compiler has optimized for size, despite our use of the -O0 option. Given the generated machine instructions, there is nowhere to put a breakpoint that will only be hit when the conditional at line 8 evaluates to non-zero. A breakpoint at 0x00001fc5 or 0x00001fc7 would be hit whenever the conditional is evaluated, which is always. A breakpoint at 0x00001fd7 would be hit whenever the function returns, which is always as well. Unfortunately, gdb places the breakpoint at 0x00001fc9, which is actually the opposite of what we intended, because it only gets hit when the conditional evaluates to zero. This is why the program exits normally without ever hitting the breakpoint. I consider this to be a bug in gdb; it would be better, I think, if it would just fail and give an error when we try to set the breakpoint. Of course, it may be a bug in gcc that it optimizes away our multiple exit points with optimization off. But hey, what do you expect from free software?

There are several workarounds for this problem. One would be to re-write your source code. (No, that’s not a joke. See Part II of this series.) Another workaround, if you only want to break on the result of a conditional, is to use a conditional breakpoint:

(gdb) delete break
Delete all breakpoints? (y or n) y
(gdb) break *0x00001fc5 if $eax != 0
Breakpoint 1 at 0x1fc5: file returnbreak.c, line 8.
(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x00001fc5 in HelloWorld at returnbreak.c:8
	stop only if $eax != 0
(gdb) run
Starting program: /Users/jeff/Desktop/returnbreak 

Breakpoint 1, 0x00001fc5 in HelloWorld () at returnbreak.c:8
8		if (ShouldReturn())
(gdb) c
Continuing.

Program exited normally.

To summarize, if you find that your breakpoints are not getting hit, you now know who to blame. Namely, yourself. It’s almost certain that your Xcode project settings are wrong.

Gruber does a snow job on Snow Leopard

September 5th, 2009

John Gruber holds Apple blameless for shipping Mac OS X 10.6.0 with an outdated version of Adobe Flash Player, a version with known vulnerabilities that have been exploited in the wild. The essence of his absolution is the following timeline: “Adobe released version 10.0.32.18 of Flash on July 30. Snow Leopard went GM on Friday August 7″.

It is true that Adobe released Flash Player 10.0.32.18 to the public on July 30. However, with regard to the version of Flash Player included with Snow Leopard, that date is largely irrelevant. On July 21, Adobe released a security bulletin to the public: “Adobe is aware of reports of a potential vulnerability in Adobe Reader and Acrobat 9.1.2 and Adobe Flash Player 9 and 10.” The next day, there was a public follow-up: “A critical vulnerability exists in the current versions of Flash Player (v9.0.159.0 and v10.0.22.87) for Windows, Macintosh and Linux operating systems, and the authplay.dll component that ships with Adobe Reader and Acrobat v9.x for Windows, Macintosh and UNIX operating systems. This vulnerability (CVE-2009-1862) could cause a crash and potentially allow an attacker to take control of the affected system. There are reports that this vulnerability is being actively exploited in the wild via limited, targeted attacks against Adobe Reader v9 on Windows. We are in the process of developing a fix for the issue, and expect to provide an update for Flash Player v9 and v10 for Windows, Macintosh, and Linux by July 30, 2009″.

It is crucial to note that these were all public releases. Since Apple ships Adobe Flash Player with Mac OS X, Apple and Adobe undoubtedly have a private relationship. One would presume that Adobe privately discloses security vulnerabilities in the OS X version of Flash Player to Apple in advance of public announcements and that Adobe privately provides Apple with versions of Flash Player to test on OS X prior to public release. If these things do not occur privately, then both companies ought to be blamed for failing to follow industry best practices.

I’m not sure where Gruber gets the August 7 date. (Perhaps an email from Phil?) Snow Leopard build 10A432 was seeded to eligible ADC members on August 12. If Apple had already declared build 10A432 the GM before seeding it to developers for testing, that would be completely irresponsible (though sadly, not unprecedented). In any case, if the 10A432 seed had turned up a show-stopping bug, Apple could have un-declared it GM. Is allowing an attacker to take control of a system via a web browser not a show-stopper? Gruber asks, “Should Apple have postponed Snow Leopard for another month?” Despite the rhetorical nature of the question, I’ll answer: maybe they should have. Or at least, they should have postponed it 4 days. At WWDC, Apple told us that Snow Leopard would be released in September. Last time I checked, August 28 is not in September. Even delaying Snow Leopard a month would have allowed Apple to ship on time, in September.

Setting aside these more relevant dates, let’s just accept Gruber’s 8-day window for the sake of argument. “Does anyone really think that Apple should have replaced the single-crashiest piece of software in Mac OS X with a new untested version just eight days before going GM?” Yes, I do. We’re not talking about a major update here — obviously Apple should not switch from Flash 9 to Flash 10 eight days before GM. Apple had already pulled the trigger on Flash 10 for Snow Leopard. When security vulnerabilities come to light, fixes must be released quickly. Eight days of testing the update from Flash Player 10.0.22.87 to 10.0.32.18 really should have been sufficient for Apple. If a critical vulnerability was discovered in Mac OS X, Apple should be able to ship a fix within 8 days, if not sooner. Indeed, there were 8 days between Adobe’s security bulletin and the release of the updated Flash Player.

Apple’s record for shipping timely fixes to security vulnerabilities is poor. For example, Apple ‘distinguished’ themselves by being the only vendor in the world to fail to join the coordinated effort to release a fix for the Kaminsky DNS vulnerability on all platforms on the same day. Instead, Apple took its own sweet time, which was particularly egregious for Mac OS X Server customers. (Though I use OS X on my personal computers, I’m glad my web host does not use OS X on their servers.) I personally was aware of the ‘Safari RSS’ vulnerability for months while it remained unpatched and could have easily exploited any visitor to my web site if I had wanted. Fortunately for you, I’m not malicious. Not criminally malicious, anyway.

Apple hot-swapped Mac OS X 10.5.8

September 1st, 2009

There has been some confusion in the net-o-sphere over the existence of two Mac OS X 10.5.8 builds: 9L30 and 9L31a. I think it’s time to clear up that confusion, now that Max OS X 10.6.0 has been released and nobody cares anymore about 10.5.x.

The Max OS X build version is stored in the following file on your hard drive:

/System/Library/CoreServices/SystemVersion.plist

On my machine, the build is 9L30. I installed 10.5.8, as usual, via the combo updater (because I’m a paranoid superfreak who also repairs permissions and offers sacrifices to Demeter). This was a prudent three days after 10.5.8 was released, though the combo updater was downloaded two days after release.

Lately (in the Holocene epoch), the Mac OS X installers have come in ‘flat’ package format (i.e, “smooth and even; without marked lumps or indentations” or “lacking interest or emotion; dull and lifeless”). This makes them opaque to the casual observer. Fortunately, I am Klondike Kat. The installer .pkg file is actually a xar archive that can be read and extracted with /usr/bin/xar. To extract the package contents into the current working directory:

$ xar -xf /Volumes/Mac\ OS\ X\ Update\ Combined/MacOSXUpdCombo10.5.8.pkg

This gives us an ‘old-style’ (Pleistocene) .pkg file whose contents we can view in Finder. The package contains, among other things, a Payload. Be careful not to ignite it, otherwise you may require intensive care, if not AppleCare. The Payload is a gzip archive, so I slapped a .gz extension on the file and gunzip‘ed it. After extracting that archive, you’re left with … yet another archive. (Apparently the Matryoshka method of software distribution.) The new Payload can be read and extracted with /bin/pax:

$ pax -f Payload -r *SystemVersion.plist

The SystemVersion.plist from my original installer is for build 9L30, but the one from the installer I downloaded today is for build 9L31a. Thus, we have to conclude that Apple ‘hot swapped’ Mac OS X 10.5.8. That is, they switched Mac OS X builds after release without bumping the version number.

Why Apple did this remains a mystery. Usually software developers do it when they discover an issue shortly after release but don’t want to go to the trouble of making a public announcement of a new version. What was the issue, and do those of us who have build 9L30 installed still suffer from the issue? For the answer to those questions, you’ll have to read the release notes. ;-)

Snow Leopard hidden Dock preference

August 28th, 2009

In Mac OS X 10.5 and earlier, clicking and holding on an item in the Dock would bring up a contextual menu for that Dock item. In Mac OS X 10.6, popularly known as Snow Leopard, unpopularly known as Leopard Service Pack 1 or “What am I supposed to do with my Power Mac G5 Quad?”, this behavior has changed. One of the new non-Exchange non-features in Snow Leopard is Dock Exposé. Clicking and holding on an application icon in the Snow Leopard Dock invokes Exposé for that application. This is the same effect you see when pressing the F10 key in Tiger and later.

If for some bizarre reason you prefer the old behavior (you backward, reactionary, Obama-hating Luddite), it is possible to bring it back. This is a Lap Cat Software exclusive — you heard it here first, folks! Launch the Terminal application and enter the following:

defaults write com.apple.Dock show-expose-menus -bool no; killall Dock

You’re welcome. Remember me fondly in your will.

It’s over

August 6th, 2009

I figured I’d cruise, at least through the Spring. However, the wheels on the bus go round and round.

rdar://problem/7125338

I am still master of my domain. Although I need to renew before it expires in three weeks.

Boycott Radar

August 5th, 2009

Until further notice, I’m boycotting Radar. No more filing bugs, no more responding to bugs. For me, Radar is both frustrating beyond belief and also a waste of time. I recommend that my fellow Mac developers join my boycott, if for no other reason than to preserve whatever sanity and mental health you have remaining. I’ve come to the conclusion that life without Radar will be happier and more productive.

In order of importance (and annoyance), here are my major complaints about Radar:

  1. Mindless responses to bugs from Apple zombies … err, employees
    I expect a knowledgeable person to read and evaluate my bugs carefully. I’m sick and tired of getting stupid, sometimes irrelevant responses. It’s clear in many cases that the Apple employee was basically skimming for keywords and didn’t bother to actually read the bug. And I’m far from alone here: I’ve heard numerous examples (otherwise known as horror stories) from other developers of the same kind of maddening response to their bugs. We developers spend a lot of time discovering, investigating, and reproducing these bugs for Apple, without receiving any compensation. Inexplicably, though, Apple employees are dismissive of our help. They seem to care more about closing the Radar than fixing the bug that the Radar reports.
  2. Duplicate bugs are second class citizens
    If your bug gets marked as a dupe, you’re doomed. Don’t expect to ever hear about it again, not even if it’s fixed. Apple’s canned response says, “To request the status of the original bug, please update your report directly via the Apple Bug Reporter”, which is ridiculous, because you could have dozens or even hundreds of duplicates, and it can sometimes take years for a bug to get fixed, so how often are you supposed to make status requests?
  3. No searchable bug database
    If you’re lucky, an Apple engineer on a mailing list may tell you that your problem is a known issue. If not, you could flail around for days trying to figure out why your code that should work doesn’t work, because of a Mac OS X bug. A number of other companies provide searchable bug databases to their developers, why can’t Apple? It’s true that sometimes your bug reports contain confidential information that you don’t want to share with other developers (your competitors, for example), but often they don’t, and it would be nice to have an ‘opt in’ option to allow other developers to see your bug. It’s also true that Apple needs to protect its secrets; however, Apple should realize that not everything is or needs to be secret, and as ADC members we’re already bound by Non-Disclosure Agreements, so what’s the point of being under an NDA with Apple if Apple never discloses anything to us? The existence of Open Radar demonstrates how ludicrous it is that Apple does not provide a searchable bug database themselves. Although I don’t post my bugs on Open Radar because I don’t have a Google account, I do have a list here.
  4. Wasting my time asking me to verify unfixed bugs
    Apple employees seem to think third party developers have nothing better to do than perform unpaid QA work for Apple. A number of times, I’ve gotten requests to verify that a bug still exists in software update X, and indeed it does still exist in software update X, as demonstrated by the very steps to reproduce that I listed in my bug report. Did anyone at Apple even bother to follow my steps? (That’s a rhetorical question — obviously, no.) What were you thinking here, that my bug would magically disappear without having to do anything? Sorry, your deus ex machina failed to show up, stop wasting my time and start fixing the bug. If Apple is understaffed, and its employees are overworked and don’t have enough time to do this themselves, that’s not my fault. If I hear one more excuse about Apple not having the resources, I’m going to puke. Or punch someone. Or puke on someone’s fist. Apple makes more than a billion dollars a quarter in profit. My company makes slightly less than that.

What I’ve come to realize is that we developers don’t need Radar. Apple needs us, but we don’t need them (for this, anyway). The time between filing a bug and seeing a fix for the bug shipped in a Mac OS X software update is usually quite long, sometimes infinitely long. If I discover a Mac OS X bug that affects my software, I can’t wait for a fix from Apple, I have to write a workaround immediately. Thus, by the time I file a bug, I don’t really need a fix for it. The sole purpose of filing the bug is to help other developers and to make the Mac OS X platform better. Essentially, it’s charity work. If Apple makes charity work for them really difficult and annoying, then I’m going to find something better to do, like adopt a cat, or a highway.

Vienna 2.3.2 security update

July 27th, 2009

I’ve just released the security update Vienna 2.3.2. Security is no laughing matter, so all Vienna users need to update to version 2.3.2. Release notes are here.

The technical details of the vulnerability are instructive. The problem was this line of code:

returnCode = NSRunAlertPanel(alertTitle, alertBody, NSLocalizedString(@"Delete", nil), NSLocalizedString(@"Cancel", nil), nil);

Well, that’s not entirely true. The problem was that line combined with certain assignments of alertBody, for example:

alertBody = [NSString stringWithFormat:@"Are you sure you want to unsubscribe from \"%@\"? This operation will delete all cached articles.", [folder name]];

By default, the value of [folder name] comes from a most trustworthy source: the internet. If, by chance or design, the name contains conversion specifiers, you’re in big trouble. For a discussion of this kind of vulnerability, see Apple’s Secure Coding Guide. The Secure Coding Guide is a must read for Mac Developers. (I also recommend this. Tell them Jeff sent you.)

One simple fix for the vulnerability is as follows:

returnCode = NSRunAlertPanel(alertTitle, @"%@", NSLocalizedString(@"Delete", nil), NSLocalizedString(@"Cancel", nil), nil, alertBody);

With this fix, the alertBody string — which contains the unvalidated folder name — is no longer treated as a format string.

You need to be very careful about using NSRunAlertPanel() and similar functions. Unfortunately, the API and documentation are quite bad. The Discussion doesn’t even mention that the second argument is a format string; for that, you have to read the documentation for another function. Furthermore, it’s absolutely insane to put three unrelated items between the format string and the optional arguments for the format string.

As you know, however, you go to war with the API you have, not the API you might want or wish to have at a later time.

WWDC: Busted

June 16th, 2009

WWDC is over, and I’m now home safe, somewhat sound. I truly enjoyed my time at DEN, as well as the brief visit to San Francisco in between. I met some people, failed to meet a lot of people, saw some old friends and old enemies (I’ll let you decide who’s who), fell in love, got married, got divorced, killed a man, and won the NBA championship. Ok, maybe not all that, but I did pay too much for brunch.

For me, the most useful part of WWDC was the labs. At no other time of the year do you get unfiltered, one-on-one contact with Apple engineers. Surprisingly, I even managed to avoid punching any of them. I spent hours in the lab talking with the QuickTime, WebKit, and CFNetwork teams about various issues I’ve encountered. One engineer even volunteered to exercise his Gdb Fu on my MBP.

There’s much more to WWDC than just the technical side, though. In addition to the valuable information I learned, I was also able to bring home a backpack, three shirts, a Red Sweater button, and the common cold.

I do have one complaint about WWDC (not the food). We stood in line for over an hour — outside in the chilly wind — to get into the keynote, but it wasn’t until 15 minutes after the keynote started that we finally got in … to the overflow room. I understand that there’s not enough space for everyone in the main room, but Apple knew well in advance both what time the keynote starts and how many people were attending WWDC, so there is absolutely no excuse for failing to open the doors in time for everyone to get in the building and sit down. Really, it’s shameful. I would like to hear an apology from Apple for this major logistical screwup. It gave me a bad impression at the very beginning of my first WWDC and first keynote. Not to mention that tickets are quite expensive, yet non-attendees following on the internet had better access to the keynote than me. WWDC organizers, you suck!

I’m not allowed to say anything else about the conference, because of the NDA. I may be breaking it just by telling you I was there. Nonetheless, I’m going to share one little Snow Leopard secret with you. To distinguish it from Leopard, the latest WWDC seed has a new default Desktop background image: Hello Kitty.

WWDC or bust

June 5th, 2009

Thanks to the generosity of Rogue Amoeba, the Ford Foundation, and listeners like you, I’ll be attending WWDC this year. If you want to find me there, I’ll be the one wearing a Rogue Amoeba T-shirt.

Actually, I’ll be one of the ones wearing a Rogue Amoeba T-shirt. The cute one.

No, sorry, that’s Paul. Let’s face it: in our fab four, I’m the Ringo. (Or for you younger folks, the Michael Anthony.) My plan is to earn millions hanging out with those other guys and making thumping noises in the background.

This will be my first time attending WWDC. I’m really looking forward to meeting fellow developers such as Rainer Brockerhoff and … umm … err … yeah. Anyway, hope to see you there!

To those poor, unfortunate souls who won’t be at the Moscone Center on Monday, I offer a consolation: the official 2009 WWDC keynote home game! The rules are straightforward. (1) Whenever Phil Schiller says something, drink. Heavily.