sal annotation (prefast) to enforce number of variadic args

346 views Asked by At

I have a variadic function:

print_n_integers(7, 1, 2, 3, 4, 5, 6, 7);

int print_n_integers( unsigned int count, ... )
{
    // use va_start etc.
}

I'd like to use Microsoft's SAL annotations from sal.h so that the Visual Studio compiler notices when the number of parameters does not match count. I can enforce count as a literal this way:

int print_n_integers (
    _Literal_ unsigned int count,
    ...
)

and Visual Studio at least has some cleverness to cope with printf(), but is there something for simple parameter count?

2

There are 2 answers

1
M Oehm On

I'd like to propose an alternative approach to your problem that doesn't use static analysis.

Even if you could get the count of the variadic arguments for static analysis, you'd still have to provide the correct count for each call. You'd also have to make sure that all arguments are (or can be promoted to) integers.

A more natural, non-variadic approach to handle a list of homogenouos types is to print an array:

    void print_nint(const int *arr, size_t n);

C99 compatible compilers can create arrays on the spot with compound literals:

    print_nint((int[]) {0, 8, 15}, 3);

This syntax lends itself to be wrapped in a macro:

    #define print_int(...)                                  \
        print_nint((int[]){__VA_ARGS__},                    \
            sizeof((int[]) {__VA_ARGS__}) / sizeof(int))

This macro does the counting and you can use it without explicit argument count:

    print_int(1, 2, 3.0, rand(), i++, j++);

The second expansion of the variadic arguments is used inside sizeof and not evaluated. In the line above, rand() is called only once and i and j are incremented only once. The floating-point argument is converted to int before printing.

Visual Studio has introduced compound literals in 2013 RC, but older versions don't support them. Unfortunately, compound literals also render your code unusable for C++ compilers. For these cases, you could rework the macro to define a temporary array:

    #define print_int(...) {                                            \
        int PrintMe_[] = {__VA_ARGS__};                                 \
        print_nint(PrintMe_, sizeof(PrintMe_) / sizeof(*PrintMe_));     \
    }

This strategy works only if the function is called in void context, however.

If you use only the macro, the array and its size are consistent. But you can publish the underlying implementation and annotate it with SAL:

    void print_nint(_In_reads_(n) const int *arr, size_t n);

I don't advocate the gratuitous use of macros, but in this case, the macro takes the role of a template; it can reduce dangerous redundancy.

0
Daniel Rose On

Unfortunately there currently is no annotation in SAL for this case.

The support for the printf/scanf/scanf_s class of functions is hardcoded into the analyzers and accessed via the _Printf_format_string_ / _Scanf_format_string_ / _Scanf_s_format_string_ annotation, respectively (and for special cases, _Printf_format_string_params_ / _Scanf_format_string_params_ / _Scanf_s_format_string_params_). So your only chance would be to lobby the code analysis team to add support for your use case.