Force SWIG generated Lua wrapper to allow extra arguments for varargs functions

时间:2018-06-04 17:40:34

标签: lua swig

When I generate a wrapper of a varargs function with SWIG it inserts code that checks the exact number of arguments passed to the function, for example given:

%inline %{
void foobar(const char *fmt, ...) {}

The generated wrapper always inserts:

SWIG_check_num_args("foobar",1,1)

How can I work around that and allow any number of arguments larger or equal to 1?

1 个答案:

答案 0 :(得分:2)

It is possible to work around this and generate code that won't error when passed extra arguments using a series of hacks.

Essentially the hacks boil down to this observation - the only thing we can control before the SWIG_check_num_args call is the declaration and initialisation of local variables. However we can take advantage of that to make a local variable that shadows a global one and change the behaviour based on that.

%module test

%runtime %{
static inline int SWIG_check_num_args_real(lua_State* L, const char *fn, int min_args, int max_args) {
    SWIG_check_num_args(fn, min_args, max_args);
    return 1;
fail:
    return 0;
}
#undef SWIG_check_num_args
#define SWIG_check_num_args(name,a,b) if (!SWIG_check_num_args_real(L,name,a,_global_is_varargs?1024:b)) goto fail; 
static const int _global_is_varargs=0;
%}

%typemap(in) (const char *fmt, ...) (const int _global_is_varargs=1) %{
    $typemap(in,const char *) // Default string stuff
    // TODO: Do some real work with the rest of the arguments here
%}

%inline %{
void foobar(const char *fmt, ...) {}
void boring(int i) {}
%}

To make that concept work we need to insert some code that replaces the default macro SWIG_check_num_args with one we control, ideally without completely re-writing the macro just in case it changes later. To do that we setup an inline function so that we can have another fail label for the macro to hit, but can apply the original definition of the macro before a later #undef.

With that inline function in place we can then redefine the macro to one that's slightly more favourable, it use a variable called _global_is_varargs to dynamically modify the 3rd argument to the macro and increase it if we're in a varargs kinda situation. (1024 is totally arbitrary, INT_MAX or some implementation specific limits would work equally well).

The variable we're planning on shadowing needs to start with the magic prefix _global_ to avoid the automatic appending of a suffix onto local variable names. Since everything is const a smart compiler can probably do this with no extra runtime overhead too.

Then we can write typemaps that actually create a local variable to mask the global one and do some real work on the Lua arguments. I did that as a multi-argument typemap in my example, but that may not be strictly necessary. (I liked the exact matching anyway).

With this we can then call the functions as expected (although it doesn't do anything useful, that's an exercise in preprocessor/FFI/ABI/gcc extension fun):

Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> m=require('test')
> m.boring()
stdin:1: Error in boring expected 1..1 args, got 0
stack traceback:
        [C]: in function 'test.boring'
        stdin:1: in main chunk
        [C]: in ?
> m.boring(1)
> m.foobar()
stdin:1: Error in foobar expected 1..1024 args, got 0
stack traceback:
        [C]: in function 'test.foobar'
        stdin:1: in main chunk
        [C]: in ?
> m.foobar('')
> m.foobar('',1)
> m.foobar('',1,2,3)

It doesn't break normal checks and enforces something sensible on function call arguments still.