V576. Incorrect format. Consider checking the Nth actual argument of the 'Foo' function.
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);
Wide character strings
Visual Studio has one displeasing feature when it interprets the string format in a non-standard way to print wide characters. Therefore, the analyzer can diagnose errors in code like the following sample:
const wchar_t *p = L"abcdef";
wprintf(L"%S", p);
In Visual C++, "S" is meant to be used to print a string of the "const char *" type, so from its viewpoint, the correct version of the code above should look like this:
wprintf(L"%s", p);
Starting with Visual Studio 2015, the developers offer a solution to this issue for the sake of compatibility. To make your code compatible with ISO C (C99), you need to specify the _CRT_STDIO_ISO_WIDE_SPECIFIERS macro for the preprocessor.
In that case, the code:
const wchar_t *p = L"abcdef";
wprintf(L"%S", p);
will be treated as correct.
PVS-Studio knows about the _CRT_STDIO_ISO_WIDE_SPECIFIERS macro and takes it into account when performing the analysis.
By the way, if you have the ISO C compatibility mode enabled (i.e. declared the _CRT_STDIO_ISO_WIDE_SPECIFIERS macro), you can restore the old-type conversion in certain places by using the "%Ts" format specifier.
This story with wide characters is quite complicated and is outside the scope of this documentation. To figure it all out, see the following resources:
- Bug 1121290 - distinguish specifier s and ls in the printf family of functions
- MBCS to Unicode conversion in swprintf
- Visual Studio swprintf is making all my %s formatters want wchar_t * instead of char *
- Update. In 2019, an article appeared which explains why confusion takes place: The sad history of Unicode printf-style format specifiers in Visual C++.
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:
- Wikipedia. Printf.
- MSDN. Format Specification Fields: printf and wprintf Functions.
This diagnostic is classified as:
You can look at examples of errors detected by the V576 diagnostic. |