56

I'm getting the following gcc format-truncation warning:

test.c:8:33: warning: ‘/input’ directive output may be truncated writing 6 bytes into a region of size between 1 and 20 [-Wformat-truncation=]
snprintf(dst, sizeof(dst), "%s-more", src);
                             ^~~~~~
test.c:8:3: note: ‘snprintf’ output between 7 and 26 bytes into a destination of size 20
snprintf(dst, sizeof(dst), "%s-more", src);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

on code like this:

char dst[20];
char src[20];
scanf("%s", src);
snprintf(dst, sizeof(dst), "%s-more", src);
printf("%s\n", dst);

I'm aware that it might be truncated - but this is exactly the reason why I'm using snprintf in the first place. Is there a way how to make it clear to the compiler that this is intended (without using a pragma or -Wno-format-truncation)?

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
Marius Melzer
  • 863
  • 1
  • 7
  • 10
  • By increasing the length of the destination array? – Weather Vane Jul 26 '18 at 08:30
  • What compiler version are you using? What compilation flags are you including? I am trying with gcc -W -Wall -c test.c (gcc 5.4.0) and it compiled fine without any warning message. – aicastell Jul 26 '18 at 08:37
  • 3
    @aicastell this warning has been introduced with gcc 8.1 – Jabberwocky Jul 26 '18 at 08:38
  • If this is a specific problem of a given gcc version, I think that information should be included in the original question. – aicastell Jul 26 '18 at 08:41
  • It's interesting that it chooses to warn about this, but not the dodgy `scanf`. – Ian Abbott Jul 26 '18 at 10:21
  • Yes, I'm using gcc 8.1, sorry for missing out on that information. The scanf is not like this in the original code and I can't change the length of the destination array - it's as I wrote only minimal example code that spits out the same warning – Marius Melzer Jul 26 '18 at 11:06
  • 1
    @WeatherVane maybe there is a good reason to specify this destination size. If no, he would not have used snprintf no? – fralbo Oct 01 '18 at 07:51
  • @fralbo perhaps it is the string length he wants to limit rather than its storage size. – Weather Vane Oct 01 '18 at 07:54
  • @WeatherVane possible but if I follow `ret = snprintf()` call with `if (ret < 0)` we don't get any warning which is INCORRECT! on truncation, ret will be positive. Inccreasing the destination array doesn't solve anything. GCC doesn't care of it, it only verifies if the return value is chekced but badly from my point of view. – fralbo Oct 01 '18 at 08:22

4 Answers4

53

  1. The warning was added in gcc7.1, see gcc7.1 release changes.
  2. From gcc docs:

Level 1 of -Wformat-truncation [...] warns only about calls to bounded functions whose return value is unused and that will most likely result in output truncation.

  1. The issue was a bug report and was closed as NOTABUG:

Unhandled output truncation is typically a bug in the program. [...]
In cases when truncation is expected the caller typically checks the return value from the function and handles it somehow (e.g., by branching on it). In those cases, the warning is not issued. The source line printed by the warning suggests that this is not one of those cases. The warning is doing what it was designed to do.

  1. But we can just check the return value of snprintf, which returns a negative value on error.

#include <stdio.h>
#include <stdlib.h>
int main() {
    char dst[2], src[2] = "a";

    // snprintf(dst, sizeof(dst), "%s!", src); // warns

    int ret = snprintf(dst, sizeof(dst), "%s!", src);
    if (ret < 0) {
         abort();
    }

    // But don't we love confusing one liners?
    for (int ret = snprintf(dst, sizeof(dst), "%s!", src); ret < 0;) exit(ret);
    // Can we do better?
    snprintf(dst, sizeof(dst), "%s!", src) < 0 ? abort() : (void)0;
    // Don't we love obfuscation?
#define snprintf_nowarn(...) (snprintf(__VA_ARGS__) < 0 ? abort() : (void)0)
    snprintf_nowarn(dst, sizeof(dst), "%s!", src);
}

Tested on https://godbolt.org/ with gcc7.1 gcc7.2 gcc7.3 gcc8.1 with -O{0,1,2,3} -Wall -Wextra -pedantic. Gives no warning. gcc8.1 optimizes/removes the call to abort() with optimization greater than -O1.

Oddly enough, when compiling as a C++ source file, the warning is still there even when we check the return value. All is fine in C. In C++ prefer std::format_to anyway. So:

  1. We can just use compiler specific syntax to disable the warning.

#include <stdio.h>    
#include <stdlib.h>
int main() {
    char dst[2];

    char src[2] = "a";
    // does not warn in C
    // warns in C++ with g++ newer than 10.1 with optimization -O2
    int ret = snprintf(dst, sizeof(dst), "%s!", src);
    if (ret < 0) {
         abort();
    }

    // does not warn in C
    // still warns in C++
    ret = snprintf(dst, sizeof(dst), "%s!", "a");
    if (ret < 0) {
         abort();
    }

    // use compiler specific pragmas to disable the warning
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
    snprintf(dst, sizeof(dst), "%s!", "a");
#pragma GCC diagnostic pop

// wrapper macro with compiler specific pragmas
// works for any gcc
// works from g++ 10.1
#ifndef __GNUC__
#define snprintf_nowarn  snprintf
#else
#define snprintf_nowarn(...) __extension__({ \
    _Pragma("GCC diagnostic push"); \
    _Pragma("GCC diagnostic ignored \"-Wformat-truncation\""); \
    const int _snprintf_nowarn = snprintf(__VA_ARGS__); \
    _Pragma("GCC diagnostic pop"); \
    _snprintf_nowarn; \
})
#endif
    snprintf_nowarn(dst, sizeof(dst), "%s!", "a");
}
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • 19
    Note that `snprintf()` only returns a negative number on error, though. On truncation, it returns the number of characters it would have written, had the buffer been long enough. – nafmo Sep 13 '18 at 07:36
  • This solution does not work in my case. Please take a loot: https://godbolt.org/z/4r7sT49hs – Tharindu Sathischandra Feb 01 '22 at 08:10
  • You are using C++ compiler. – KamilCuk Feb 01 '22 at 09:25
  • I prefer `#define saprintf(array, ...) (void)(snprintf(array, sizeof(array), __VA_ARGS__)<0?abort():0)` so I can D.R.Y. off the first argument. – user3710044 Jul 24 '23 at 10:15
  • @user3710044 I would recommend to not prefer that, for me it sounds confusing, dangerous and limited - consider `char *buf = (char[200]){}; saprintf(buf, "");`. `snprintf` has perfectly established API, and passing a pointer+size is perfectly readable and the preferred way in C language. – KamilCuk Jul 24 '23 at 10:44
  • You find it confusing because GCC is treating arrays (where it knows the size) differently from pointers where you have to tell it. If anything (IMO) this improves the problem because it explicitly notes that this printf is to an ARRAY not a pointer and we know that it has a fixed size that we want the compiler to truncate to. This is how snprintf is supposed to work, it is not expected that it will always be a odd variant of "asprintf". Also, if you want it to complain about pointers I suggest you add some sort of static assert perhaps like `({enum{_x=1/(sizeof(array)-sizeof(char*))};}),` – user3710044 Jul 24 '23 at 13:43
12

This error is only triggered when length-limited *printf functions are called (e.g. snprintf, vsnprintf). In other words, it is not an indication that you may be overflowing a buffer, as may happen with sprintf; it only notifies you that you aren't checking whether snprintf is doing its job and truncating. (Side note: snprintf always null-terminates, so this can't result in a non-terminated string.)

Knowing that, I'm much more sanguine about disabling it globally using -Wno-format-truncation, rather than trying to coax gcc into ignoring a specific instance.

Daniel Griscom
  • 1,834
  • 2
  • 26
  • 50
  • I don't see an issue with testing the return value of `snprintf` to avoid the warning. Plus that means you can make sure your code works as expected. You can use a limit in the format string too (`%.123s`). – Alexis Wilke Dec 03 '21 at 03:37
  • 2
    @AlexisWilke If I don't care whether it's truncated, why should I have to test for it? It's like using a `max()` function, and then being forced to test whether the limit was actually hit. – Daniel Griscom Dec 04 '21 at 04:05
  • @DanielGriscom If you really don't care, why not use the `snprintf_nowarn` macro as in the answer provided by @KamilCuk ? – Johan Bezem Aug 08 '22 at 09:25
6

This page was useful to me:
https://www.fluentcpp.com/2019/08/30/how-to-disable-a-warning-in-cpp/

You could resolve the issue for a gcc/clang compiler by doing this:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
    snprintf(dst, sizeof(dst), "%s-more", src);
#pragma GCC diagnostic pop

The webpage above also has a solution for Visual Studio compiler warnings.

Basirk
  • 353
  • 2
  • 7
  • This is the best solution. Thanks for the link. – Lehrian Oct 18 '21 at 21:18
  • 2
    they can resolve the issue by removing that nonsense from gcc. if we truncate things in sprintf it's because we WANT it to be truncated (such as hours and dates, of which we know it'll never be 'billions' but just '31' at most ;) and no we shall not include 'pragmas' in our code purely for 'GCC' when it's supposed to compile with everything else too. it never was there before. it can be removed again now. oh and btw. if i don't specify -Wall i don't want to see warnings. especially not about how i try to put a date into a %d field ;) i'll get back to them when a month has 2 billion days. – HRH Sven Olaf of CyberBunker Dec 16 '21 at 17:08
2

Also, Introducing a volatile temporary variable for the destination size is also a workaround here.

char dst[20];
char src[20];
volatile int dst_size = sizeof(dst);
snprintf(dst, dst_size, "%s-more", src);

As suggested by Martin Sebor, you may also use this snprintf_trunc macro;

#define snprintf_trunc(dst, size, ...)   \
    do {                                 \
      volatile size_t n = size;          \
      snprintf (dst, n, __VA_ARGS__);    \
    } while (0)
Tharindu Sathischandra
  • 1,654
  • 1
  • 15
  • 37