Thursday, 1 November 2018

Warnings Series - Format

In this series of posts I'm discussing how compiling with multiple compilers and warnings enabled can help to catch errors. In the last post I discussed how to turn return type warnings into errors. In this post, I'm going to look at another warning that I often promote to an errors.

Background

As discussed in my previous posts, you can turn specific warnings into error using the -Werror=warning-name command line flag on GCC and Clang. In some cases you can also annotate code in order enable checking it for a warning type.

Format

The format warning lets you know when you have used the incorrect format specifier for the printf family of functions. For example:

1
2
long i = 4294967295;
printf("%d\n", i);

In the above example, using %d instead of %ld may print the value -1 instead of the expected value 4294967295. For a simple cause of a log statement, the incorrect value might not matter, however, if you are serializing a value to a file it may cause issues for your users.

In another example:

1
2
long i = 1;
printf("%s\n", i);

You are attempting to print a number using the string format specifier. This specifier expects a null terminated string and as a result it will cause undefined behaviour which could create security and stability issues.

Catching the error

Clang

When compiling with Clang this is a warning by default when using no compiler flags. To promote the warning to an error you can use -Werror=format. Below is an example of the warning from Clang:

1
2
3
4
5
6
7
8
9
<source>:23:18: warning: format specifies type 'int' but the argument has type 'int64_t' (aka 'long') [-Wformat]
  printf("%d\n", i);
          ~~     ^
          %ld
<source>:24:18: warning: format specifies type 'char *' but the argument has type 'int64_t' (aka 'long') [-Wformat]
  printf("%s\n", i);
          ~~     ^
          %ld
2 warnings generated.

GCC

For GCC the warning is not enabled by default. It is included as part of the -Wall flag or can be enabled individually using -Wformat. To promote it to an error you can use -Werror=format.

Note: From v8.0 of GCC you will receive a warning with no additional compiler flags.

MSVC

With MSVC the warning for standard printf functions is enabled by default.

Attributing your own functions

It can sometimes be the case that you want to make your own function which takes a format specifier (e.g. a logging function):

1
2
3
4
5
6
7
8
9
void my_logger(int level, const char* format, ...)
{
    if(level >= LOGGING_LEVEL) {
        va_list(args);
        va_start(args, format);
        vfprintf(stderr, format, args);
        va_end(args);
    }
}

Which you can call as:

1
my_logger(WARN,"%s\n", i);

In this case the normal format warning won't appear in you code to warn you of errors. If using GCC and Clang, you can add a non-standard function attribute to tell the compiler that a function takes a format argument.

1
void my_logger(int level, const char* format, ...) __attribute__ ((format (printf, 2, 3)));

After adding this you can will receive the following warning warning:

1
2
3
4
<source>:26:27: warning: format specifies type 'char *' but the argument has type 'int64_t' (aka 'long') [-Wformat]
  my_logger(WARN, "%s\n", i);
                   ~~     ^
                   %ld

Note: This is not available on MSVC, so the addition of this attribute should be hidden behind a conditional compiler flag.

Comparing the errors

To view the errors from each compiler you can use godbolt to compare the output.

No comments:

Post a Comment