Objectivist-C

I’m Bavarious, (sporadically) writing about Objective-C, Apple frameworks and OS X.

Aug 16

A CHOCKING MYSTERY: po [NSNumber numberWithBool:NO] outputs 1

Craig Hockenberry posted this on Twitter:

(gdb) po [NSNumber numberWithBool:NO]

1

Does not inspire confidence…

Jens Ayton cleverly noted that NO is a macro, so where would GDB be getting a value from?

A few comments ensued, including speculations about tagged pointers, until Cédric Luthi mentioned how to correctly handle it:

That’s unfortunate but Foundation exports YES and NO symbols, the correct way to use it is *((char*)NO)

I wasn’t aware of that, so I decided to take a look.

In Objective-C source code using Apple’s runtime, the BOOL type is defined as:

typedef signed char     BOOL;

with corresponding boolean values:

#define YES             (BOOL)1
#define NO              (BOOL)0

This means that a boolean value is a signed, 1-byte character, with 1 representing a true value and 0 representing a false value (actually, the C language allows any non-zero value to represent a true value).

In source code, any reference to YES or NO is converted to (BOOL)1 or (BOOL)0 by the preprocessor.

However, GDB does not invoke the preprocessor. This means that in:

po [NSNumber numberWithBool:NO]

NO must be obtained from somewhere else.

In fact:

(gdb) p NO

outputs:

$1 = {<text variable, no debug info>} 0x9374bc6a73a0a1 <NO>

hence there’s a symbol called NO that was loaded onto the address 0x9374bc6a73a0a1. Since the address is different from 0, +numberWithBool: returns an NSNumber representing a true value.

Inspecting the Foundation framework, we can see:

$ nm Foundation | grep NO
…
00000000002630a1 S _NO

that Foundation indeed exports a symbol called NO. The S flag means that the symbol is in a section other than text, data or bss. Running:

$ nm -m Foundation | grep NO
…
00000000002630a1 (__TEXT,__const) external _NO

shows that NO is in the (__TEXT,__const) section. Inspecting the contents of that section:

$ nm -s __TEXT __const Foundation | sort
…
00000000002630a0 S _YES
00000000002630a1 S _NO
00000000002630a2 s ___NSOperationPrios

we can see that the symbols YES and NO use only one byte. This matches the previous definition that BOOL is one-byte long, so it’s reasonable to expect that those symbols point to a 1-byte memory region whose contents are either 1 or 0.

If we cast the NO symbol as a pointer to signed char and then dereference it to obtain its value, we get:

(gdb) p *((signed char *)NO)
$1 = 0 '\0'

As expected, we get 1 for YES:

(gdb) p *((signed char *)YES)
$1 = 1 '\001'

So the correct way to obtain NO, with the same value and representation used in an Objective-C program, is to use the expression:

*((signed char *)NO)

as pointed out by Cédric Luthi. For example:

(gdb) po [NSNumber numberWithBool:*((signed char *)NO)]
0
(gdb) po [NSNumber numberWithBool:*((signed char *)YES)]
1

Even though this mimics what happens in Objective-C source code, it’s not strictly necessary. Knowing that NO is 0 and YES is 1 allows us to write the much simpler expressions:

(gdb) po [NSNumber numberWithBool:0]
0
(gdb) po [NSNumber numberWithBool:1]
1

without having to reference the symbols exported by Foundation.

Alternatively, Core Foundation exports two constants — kCFBooleanTrue and kCFBooleanFalse — that represent the two possible boxed boolean values:

(gdb) po (id)kCFBooleanFalse
0
(gdb) po (id)kCFBooleanTrue
1

In fact, whenever Foundation/Core Foundation returns a boxed boolean value, it returns either kCFBooleanTrue or kCFBooleanFalse:

(gdb) p/a (int)[NSNumber numberWithBool:0]
$1 = 0x7f270f20
(gdb) p/a (int)kCFBooleanFalse
$2 = 0x7f270f20

and

(gdb) p/a (int)[NSNumber numberWithBool:1]
$1 = 0x7f270eb0
(gdb) p/a (int)kCFBooleanTrue
$2 = 0x7f270eb0

Note how the objects returned by +numberWithBool: have the same addresses as those Core Foundation constants. After Core Foundation is loaded and throughout the execution of a program, there are exactly two NSNumber objects representing true and false.

Also note that the addresses are even integers, hence they aren’t tagged pointers. Even though they are of type NSNumber, they are not treated as (fast-pathed) integers by Core Foundation.

Edit: Since GDB optimises its symbol lookup table, code that doesn’t reference NSNumber might yield a No symbol "NSNumber" in current context. error message. If that happens, replace NSNumber with NSClassFromString(@"NSNumber"):

po [NSClassFromString(@"NSNumber") numberWithBool:NO]

  1. railsgun reblogged this from objectivistc
  2. objectivistc posted this