Thursday, April 14, 2022

[SOLVED] Rewrite GCC cleanup macro with nested function for Clang?

Issue

I'm trying to work through an issue on a third party library. The issue is the library uses GCC's nested functions buried in a macro, and Clang does not support nested functions and has no plans to do so (cf., Clang Bug 6378 - error: illegal storage class on function).

Here's the macro that's the pain point for me and Clang:

#define RAII_VAR(vartype, varname, initval, dtor) \
    /* Prototype needed due to http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36774 */ \
    auto void _dtor_ ## varname (vartype * v); \
    void _dtor_ ## varname (vartype * v) { dtor(*v); } \
    vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)

And here's how its used (from the code comments):

 * void do_stuff(const char *name)
 * {
 *     RAII_VAR(struct mything *, thing, find_mything(name), ao2_cleanup);
 *     if (!thing) {
 *         return;
 *     }
 *     if (error) {
 *         return;
 *     }
 *     do_stuff_with_thing(thing);
 * }

The Clang User Manual states to use C++ and a lambda function to emulate. I'm not sure that's the best strategy, and a C project will likely not accept a C++ patch (they would probably tar and feather me first).

Is there a way to rewrite the macro so that's its (1) more accommodating to Clang, and (2) preserves original function semantics?


Solution

Clang doesn't support GCC nested functions, but it does support Objective C-style "blocks", even in C mode:

void f(void * d) {
    void (^g)(void *) = ^(void * d){ };
    g(d);
}

You need to invoke it with the clang command rather than gcc, and also (?) pass -fblocks -lBlocksRuntime to the compiler.

You can't use a block as a cleanup value directly, since it has to be a function name, so (stealing ideas from here) you need to add a layer of indirection. Define a single function to clean up void blocks, and make your RAII'd variable the block that you want to run at the end of the scope:

typedef void (^cleanup_block)(void);
static inline void do_cleanup(cleanup_block * b) { (*b)(); }

void do_stuff(const char *name) {
    cleanup_block __attribute__((cleanup(do_cleanup))) __b = ^{ };
}

Because blocks form closures, you can then place the operations on your variables to cleanup directly inside that block...

void do_stuff(const char *name) {
    struct mything * thing;
    cleanup_block __attribute__((cleanup(do_cleanup))) __b = ^{ ao2_cleanup(thing); };
}

...and that should run at the end of the scope as before, being invoked by the cleanup on the block. Rearrange the macro and add a __LINE__ so it works with multiple declarations:

#define CAT(A, B) CAT_(A, B)
#define CAT_(A, B) A##B

#define RAII_VAR(vartype, varname, initval, dtor) \
    vartype varname = (initval); \
    cleanup_block __attribute__((cleanup(do_cleanup))) CAT(__b_, __LINE__) = ^{ dtor(varname); };

void do_stuff(const char *name) {
    RAII_VAR(struct mything *, thing, NULL, ao2_cleanup);
    ...

Something like that, anyway.



Answered By - Leushenko
Answer Checked By - Cary Denson (WPSolving Admin)