New GCC plugin in 3rd party project (FRRouting): extended printf warnings
Hi all, the GCC wiki[1] suggests sending an e-mail when having developed a plugin, so here we are! I have, for the FRRouting project (a set of daemons implementing the various internet routing protocols, GPLv2+), implemented a plugin that does customized checking of extended printf formats. The plugin is directly derived from GCC's own gcc/c-family/c-format.c. You can find it at: https://github.com/FRRouting/frr/tree/master/tools/gcc-plugins Feature-wise, it provides these things: [or, you could look at:] https://github.com/opensourcerouting/frr/blob/build-assorted-20200717/tools/gcc-plugins/format-test.c - warnings when %zd/%zu aren't used correctly for size_t (both ways, %zu for non-size_t as well as size_t with something other than %zu) - warnings for a bunch of known platform-dependent types that should never be printed without a cast due to their unspecified nature (pid_t, uid_t, gid_t, time_t, etc.) - warnings when %Lu/%Ld aren't used for [u]int64_t (the uppercase L modifier is custom to our project and PRI*64 are redefined to use it) and the big one: - support for Linux kernel style %pI4 <> in_addr_t and similar, defined in the source-to-be-compiled through the use of pragmas like this: #pragma FRR printfrr_ext "%pI4" (struct in_addr *) #pragma FRR printfrr_ext "%pI4" (in_addr_t *) Which emits a warning when trying e.g. printfrr("%pI4", voidptr); (This is obviously also a custom extension in our printfrr(), inspired by the Linux kernel[2]) Unfortunately, there are also 2 sad bulletpoints: - I've used the Linux kernel's gcc-common.h, which is GPLv2 (no "or newer"), so we can't really distribute binaries. I'll probably look at dropping gcc-common.h, it shouldn't really be necessary. - some checks only function correctly when a 1-line patch[3] is applied to gcc. The problem is that if you do a int x; printf("%zu", (size_t)x); /* => plugin sees "unsigned long" for arg 2, no way to get size_t */ the (size_t) cast is condensed down at a *very* early stage of compilation, before the plugin can get at it. It only gets "unsigned long" (or whatever else.) If it's a straight variable, it works as expected, e.g.: size_t x; printf("%zu", x); /* => plugin sees "size_t" for arg 2 */ I'm planning to submit that patch in a separate mail, but I am not a GCC hacker at all and don't know whether this might cause any fallout. I have, however, about a year ago, discussed this on the GCC IRC channel, and whoever I talked to was saying that the line I'm deleting there might be a "premature optimization." The cases where it makes a difference are highlighted here: https://github.com/opensourcerouting/frr/blob/build-assorted-20200717/tools/gcc-plugins/format-test.c#L85-L89 Anyay, I just thought I'd announce it here for posterity and for future reference for submitting that one-liner patch. Cheers, -David [1] https://gcc.gnu.org/wiki/plugins [2] https://www.kernel.org/doc/Documentation/printk-formats.txt [3] https://github.com/FRRouting/frr/blob/master/tools/gcc-plugins/gcc-retain-typeinfo.patch
Re: New GCC plugin in 3rd party project (FRRouting): extended printf warnings
P.S.: here's the output you get from the included format-test.c: $ gcc -Wall -Wextra -Wno-unused -fplugin=./frr-format.so -fno-diagnostics-show-caret -c -o format-test.o format-test.c format-test.c: In function ‘test’: format-test.c:38:12: warning: format ‘%zu’ expects argument of type ‘size_t’, but argument 2 has type ‘long int’ (strict match required [B]) [-Wformat=] format-test.c:39:12: warning: format ‘%zu’ expects argument of type ‘size_t’, but argument 2 has type ‘int’ [-Wformat=] format-test.c:41:12: warning: format ‘%zu’ expects argument of type ‘size_t’, but argument 2 has type ‘mytype’ {aka ‘long unsigned int’} (strict match required [B]) [-Wformat=] format-test.c:43:12: warning: format ‘%zu’ expects argument of type ‘size_t’, but argument 2 has type ‘uint64_t’ {aka ‘long unsigned int’} (strict match required [B]) [-Wformat=] format-test.c:44:12: warning: format ‘%zu’ expects argument of type ‘size_t’, but argument 2 has type ‘pid_t’ {aka ‘int’} [-Wformat=] format-test.c:47:12: warning: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘int’ [-Wformat=] format-test.c:48:12: warning: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘size_t’ {aka ‘long unsigned int’} (strict match required [C]) [-Wformat=] format-test.c:50:12: warning: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘uint64_t’ {aka ‘long unsigned int’} (strict match required [C]) [-Wformat=] format-test.c:51:12: warning: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘pid_t’ {aka ‘int’} [-Wformat=] format-test.c:54:12: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 2 has type ‘int’ [-Wformat=] format-test.c:55:12: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 2 has type ‘size_t’ {aka ‘long unsigned int’} (strict match required [Z]) [-Wformat=] format-test.c:55:12: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 2 has type ‘size_t’ {aka ‘long unsigned int’} (strict match required [C]) [-Wformat=] format-test.c:57:12: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 2 has type ‘uint64_t’ {aka ‘long unsigned int’} (strict match required [Z]) [-Wformat=] format-test.c:57:12: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 2 has type ‘uint64_t’ {aka ‘long unsigned int’} (strict match required [C]) [-Wformat=] format-test.c:58:12: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 2 has type ‘pid_t’ {aka ‘int’} [-Wformat=] format-test.c:61:11: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long int’ [-Wformat=] format-test.c:62:11: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘size_t’ {aka ‘long unsigned int’} [-Wformat=] format-test.c:63:11: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=] format-test.c:64:11: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘uint64_t’ {aka ‘long unsigned int’} [-Wformat=] format-test.c:65:11: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘pid_t’ {aka ‘int’} (strict match required [C]) [-Wformat=] format-test.c:67:12: warning: format ‘%Lu’ expects argument of type ‘uint64_t’, but argument 2 has type ‘size_t’ {aka ‘long unsigned int’} (strict match required [B]) [-Wformat=] format-test.c:68:12: warning: format ‘%Lu’ expects argument of type ‘uint64_t’, but argument 2 has type ‘long int’ (strict match required [B]) [-Wformat=] format-test.c:69:12: warning: format ‘%Lu’ expects argument of type ‘uint64_t’, but argument 2 has type ‘int’ [-Wformat=] format-test.c:71:12: warning: format ‘%Lu’ expects argument of type ‘uint64_t’, but argument 2 has type ‘mytype’ {aka ‘long unsigned int’} (strict match required [B]) [-Wformat=] format-test.c:72:12: warning: format ‘%Lu’ expects argument of type ‘uint64_t’, but argument 2 has type ‘mysize’ {aka ‘long unsigned int’} (strict match required [B]) [-Wformat=] format-test.c:73:12: warning: format ‘%Lu’ expects argument of type ‘uint64_t’, but argument 2 has type ‘pid_t’ {aka ‘int’} [-Wformat=] format-test.c:76:12: warning: format ‘%Ld’ expects argument of type ‘int64_t’, but argument 2 has type ‘size_t’ {aka ‘long unsigned int’} (strict match required [X]) [-Wformat=] format-test.c:77:12: warning: format ‘%Ld’ expects argument of type ‘int64_t’, but argument 2 has type ‘long int’ (strict match required [B]) [-Wformat=] format-test.c:78:12: warning: format ‘%Ld’ expects argument of type ‘int64_t’, but argument 2 has type ‘int’ [-Wformat=] format-test.c:80:12: warning: format ‘%Ld’ expects argument of type ‘int64_t’, but argument 2 has type ‘mytype’ {aka ‘long unsigned int’} (strict match required [B]) [-Wformat=] format-test.c:81:12: warning: format ‘%Ld’ expects argumen