Saturday, December 29, 2018

Wformat-truncation

Pretty impressive new feature of gcc/g++ available in the latest devkitarm.
Look at that.


MapWindow.cxx:377:23: warning: %i directive output may be truncated writing 
    between 1 and 2 bytes into a region of size between 0 and 9 [-Wformat-truncation]
snprintf(msg, 32, "@%i,%i : %s [%ix%i]",xpos, ypos, why,
                   ^~~~~~~~~~~~~~~~~~~~~
MapWindow.cxx:689:12:      report("editing meta-layer");
                                  ~~~~~~~~~~~~~~~~~~~~
MapWindow.cxx:377:23: note: directive argument in the range [8, 16] 
MapWindow.cxx:377:23: note: directive argument in the range [8, 16]
 note: 'snprintf' output between 32 and 42 bytes into a destination of size 32

- gcc detected that I'm calling that snprintf function with a value of 'why' that is actually the "editing meta-layer" string, which is 18 bytes long
- it understood that this and some numbers had to fit within 32-bytes output
- the format characters only take 9 bytes
- xpos and ypos are 16-bit values, needing at most 5 characters to be rendered on-screen
- 18 + 9 + 4 = 31. that's the case where all the numbers take only 1 byte. We have barely enough room to display the message (and its terminating zero character)
- 18 + 9 + 5 + 5 + 2 + 2 = 41. That's the case where we have all numbers using their maximum size. Note that GCC/G++ could guess that we'll only use numbers between 8 and 16 by looking at  blockop?16:8 argument!

Having such thing can be super precious when you know what's going on and want to harden your software. Hopefully, there is no overflow expected here. Only truncation. And since the purpose is to put a message on a 32-bytes wide line, I'm fine with truncation here. I'll have to tell that to the compiler the best possible way with some #pragma.

edit: oh, actually the compiler is even smarter than I thought: it can tell whether you checked for the return value and won't bother you if you handled truncation with an if (needed > sizeof(msg) - 1)
If the output was truncated due to this limit, then the return value is the number of characters (excluding the terminating NUL byte)  which would have been written to the final string if enough space had been available.
Oh, and by the way,
*snprintf() write at most size bytes (including the terminating null byte) to str.
So in my code where I want 32 characters on the 32-char-wide screen, I should have a 33-bytes buffer and pass 33 to snprintf.

2 comments:

PypeBros said...

I wonder ... did they managed to fix val &= ~MASK when val is not an int ?

MSDN said...

did you believe it that it took us Windows 10 to have C99-compliant snprintf() ?