17 Nov 2005 cactus   » (Master)

NULL vs 0 in varags

Last week I got a weird bug report about Guikachu segfaulting at startup in g_object_newv, on AMD64. My friend Ice hooked me up with an account on an AMD64 box so I compiled a library stack with debug symbols, and it turned out the crash originated from gnome_program_init, which is declared as following:

GnomeProgram *
gnome_program_init (const char *app_id, const char *app_version,
                    const GnomeModuleInfo *module_info,
                    int argc, char **argv,
                    const char *first_property_name, ...);

This forwards the vararg list to g_object_newv which basically works like this:

prop_name = first_property_name;
while (prop_name)
{
    GType type = lookup_prop_type (prop_name);
    val = va_arg (vararg, type); /* Of course this doesn't really work like this, 
                                    there's a huge switch() block hidden beneath macro magic */
    set_prop (prop_name, val);

prop_name = va_arg (vararg, char*); }

So far so good, right? Well, gnomemm, which is a C++ library, calls gnome_program_init as such:

GnomeProgram* pProgram  = gnome_program_init (app_id_, app_version_,
                                              module_info.gobj (),
                                              argc, argv,
                                              GNOME_PARAM_POPT_TABLE, options,
                                              GNOME_PARAM_POPT_FLAGS, flags,
                                              0);

Looks harmless, doesn't it? The problem is, while in C++ 0 is used for NULL, in the above code the compiler has no way of knowing what 0's type above should be. So it goes with 'int', which is a reasonable default. This (int)0 eventually gets to g_object_newv, which reads it as a char*, and tries to see if it's NULL.

Now here comes the tricky part: remember, we're on amd64, so sizeof (int) == 4, but sizeof (char*) == sizeof (void*) == 8. So when va_arg (vararg, char*) reads the last property name, it gets 32 bits of zeros, and 32 bits of garbage. Resulting in a char* of garbage, which then gets passed to lookup_prop_type. Which makes baby Jesus cry.

So the moral of this all is:

  1. varargs are evil.
  2. varargs are even more evil than they look at first sight
  3. when using varargs from c++, make sure you use something like (void*)0 instead of vanilla 0 to denote a null pointer
  4. Have I mentioned how varargs are evil?

Latest blog entries     Older blog entries

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!