irrelevant


The GCC/clang cleanup attribute

A classic source of bugs in C code is forgetting to cleanup reasources. While we are not going to get any help directly from C to help us not doing that, there are a bunch of static analyzers and other tools that can help with detecting it. But what if there were some kind of construct that would allow us to automatically (or automagically) free resources as they go out of scope? Sounds pretty crazy right?

For some time now, gcc (and clang) has provided the cleanup attribute that can be specified on variables. A simple example:

void cleanupfn(int *v)
{
        print("value at cleanup is %d\n", *v);
}

int main(void)
{
        __attribute__((cleanup(cleanupfn))) int v;

        v = 42;

        return 0;
}

Running the above would result in "value at cleanup is 42" being printed. glib has used this for a while to provide the g_autofree macro. We can dumb it down a bit and define our own here:

static inline void __do_autofree(void *mem)
{
        void **memp = (void **)mem;

        free(*memp);
}

#define __autofree __attribute((cleanup(__do_autofree)))

Now, whenever we annotate a variable with __autofree, we can have it automatically freed when going out of scope:

int main(void)
{
        __autofree void *mem = malloc(0x1000);

        return 0;
}

Tada!

Now, can we use this for more devious stuff? How about auto unlocking a mutex? No problem:

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static inline void __unlock(pthread_mutex_t **__mtx)
{
        if (*__mtx)
                pthread_mutex_unlock(*__mtx);
}

#define __autounlock(x) \
        __attribute__((cleanup(__unlock), unused))

void main(void)
{
        __autounlock pthread_mutex_t *tmp = &mtx;
        pthread_mutex_lock(&mtx);

        /* some critical region stuff */

        return 0;
}

It's a little clunky that we have to make a local variable and then lock it. We can fix that by using a parameterized macro:

static inline pthread_mutex_t *__lock(pthread_mutex_t *__mtx)
{
        pthread_mutex_lock(__mtx);
        return __mtx;
}

#define __autolock(x) \
        __attribute__((cleanup(__unlock), unused)) \
        pthread_mutex_t *tmp = __lock(x)

void main(void)
{
        __autolock(&mtx);

        /* some critical region stuff */

        return 0;
}

If we want to lock several mutexes the tmp variable will be defined multiple times, causing compilations errors. We can fix that with some additional macro magic:

#define __glue(x, y) x ## y
#define glue(x, y) __glue(x, y)

#define __autolock(x) \
        __attribute__((cleanup(__unlock), unused)) \
        pthread_mutex_t * glue(autolock, __COUNTER__) = __lock(x)

The __COUNTER__ macro will auto increment on each use. Due to the way the C preprocessor works, we need to do an indirection using a "standard" glue macro.