V576. Incorrect format. Consider checking the N actual argument of the 'Foo' function

09.12.2014

The analyzer has detected a potential issue with the application of formatted output functions. (printf, sprintf, wprintf etc.) The formatting string doesn't correspond with actual arguments passed into the function. Let's review a simple example:

int A = 10;
double B = 20.0;
printf("%i %i\n", A, B);

According to the formatting string the 'printf' function is expecting two actual arguments of the 'int' type. However the second argument's value is of the 'double' type. Such an inconsistency leads to undefined behavior of a program. For example, it can lead to the output of senseless values.

The correct version:

int A = 10;
double B = 20.0;
printf("%i %f\n", A, B);

It's possible to cite countless examples of 'printf' function's incorrect use. Let's review some of the typical examples that are the most frequently encountered in applications.

Address printout.

The value of a pointer is quite commonly printed using these lines:

int *ptr = new int[100];
printf("0x%0.8X\n", ptr);

This source code is invalid because it will function properly only on systems which have their pointer size equal to size of 'int' type. For example In Win64 this code will print only the low-order part of the 'ptr' pointer. The correct version:

int *ptr = new int[100];
printf("0x%p\n", ptr);

The analyzer has detected the potential issue with an odd value being passed as the function's actual argument.

Unused arguments.

You can often encounter function calls in which some of these function's arguments are being unused.

For instance:

int nDOW;
#define KEY_ENABLED "Enabled"
...
wsprintf(cDowKey, L"EnableDOW%d", nDOW, KEY_ENABLED);

It is obvious that the KEY_ENABLED parameter is unnecessary here or the source code should look like this:

wsprintf(cDowKey, L"EnableDOW%d%s", nDOW, KEY_ENABLED);

Insufficient number of arguments.

A little more dangerous is the situation in which the number of arguments passed to the function is less than necessary. This can easily lead to the memory access error, buffer overflow or senseless printout. Let's review an example of memory allocation function taken from a real life application:

char* salloc(register int nbytes)
{
    register char* p;
    p = (char*) malloc((unsigned)nbytes);
    if (p == (char *)NULL)
    {
        fprintf(stderr, "%s: out of memory\n");
        exit(1);
    }
    return (p);
}

If 'malloc' returns NULL, the program will not be able to report the shortage of memory and to be terminated correctly. It instead will be terminated emergently and it will output the senseless text. In any case such a behavior will complicate analysis of the program's inoperability.

Confusion with signed/unsigned

Developers often employ the character printing specificator ('%i' for example) to output the variables of unsigned type. And vice versa. This error usually is not critical and is encountered so often than it has a low priority in analyzer. In many cases such source code works flawlessly and fails only with large or negative values. Let us examine the code which is not correct, but successfully works:

int A = 10;
printf("A = %u\n", A);
for (unsigned i = 0; i != 5; ++i)
  printf("i = %d\n", i);

Although there is an inconsistency here, this code outputs correct values in practice. Of course it's better not to do this and to write correctly:

int A = 10;
printf("A = %d\n", A);
for (unsigned i = 0; i != 5; ++i)
  printf("i = %u\n", i);

The error will manifest itself in case there are large or negative values in the program. An Example:

int A = -1;
printf("A = %u", A);

Instead of "A=-1" string the program will print "A=4294967295". The correct version:

printf("A = %i", A);

Additional features

It is possible to point to the names of user-defined functions whose format should be checked. It is assumed that formatting principle is equal to the one of printf() function.

User should write a comment of special kind near function prototype (or near its implementation, or in standard header file). Let start with the usage example:

//+V576, function:Mylog, format_arg:1, ellipsis_arg:2
Mylog("%f", time(NULL)); // warning V576

Format:

  • "function", "class" and "namespace" keys determines function name, class name (if it’s required to analyze only methods of some class) and namespace name (if it’s required to analyze only functions or class members of some namespace).
  • "format_arg" key determines number of function argument that contains format string. This argument is necessary. Numbers counts from one, not from zero, and should not exceed 14.
  • "ellipsis_arg" key determines number of function argument with ellipsis (three dots). This number is bound by the same restrictions as the one given by format_arg key. In addition, ellipsis_arg number should be greater than format_arg (because ellipsis can only be the last argument). This key is also nessesary.

At last, here is full usage example:

// Warn when in C method of class B from A namespace
// arguments, counting from third one, does not
// correspond to the format line in the second argument 
//+V576,namespace:A,class:B,function:C,format_arg:2,ellipsis_arg:3

Additional reference:

You can look at examples of errors from real projects which were detected by this diagnostic message.