Mark your variadic logging function with __format_string to have PREfast catch format specifier errors

There are a handful of Problems (with a capital P) which occur over and over again in programming. One of them is Logging.

It is incredibly convenient to use the variadic printf function to log strings with values of common types embedded in them:

// spot the bug
LOG(L"Measurement shows %lg% deviation", 100.0 * abs(expected - actual) / expected);

However, printf is very error prone. It is very easy to use the wrong format specifier like %d instead of %Id, or to forget to escape a special character like % or \.
In particular, the above line contains a bug.

Static code analysis tools like PREfast are quite good at catching these kinds of errors. If my LOG macro was something like this, PREfast would catch the bug:

#define LOG(fmt, ...) wprintf(fmt L"\n", __VA_ARGS__)

This works because PREfast knows that the first argument to wprintf is a format string, and can match up the format specifiers with the trailing arguments and verify that they match.

If you implement your own variadic logger function, though, PREfast doesn't necessarily know that the last explicit argument is a format specifier - you have to tell it. For example, PREfast will NOT catch format specifier issues if the LOG macro is defined like this:

// PREfast doesn't know Format is a format string
interface IMyLogger { virtual void Log(LPCWSTR Format, ...) = 0; };
extern IMyLogger *g_Logger;
#define LOG(fmt, ...) g_Logger->Log(fmt, __VA_ARGS__)

How do you tell it? Well, let's look at the declaration of wprintf. It's in (SDK)\inc\crt\stdio.h:

_CRTIMP __checkReturn_opt int __cdecl wprintf(__in_z __format_string const wchar_t * _Format, ...);

The relevant part here is __format_string. So the fixed IMyLogger declaration looks like this:

// Now PREfast can catch format specifier issues
interface IMyLogger { virtual void Log(__format_string LPCWSTR Format, ...) = 0; };
extern IMyLogger *g_Logger;
#define LOG(fmt, ...) g_Logger->Log(fmt, __VA_ARGS__)

Comments (0)

Skip to main content