https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88251

--- Comment #4 from Martin Sebor <msebor at gcc dot gnu.org> ---
The warning in the test case in comment #2 is due to the warning at this level
assuming the "worst case" result from the first call.  Below is a simpler
example to make it clear.  The result of the first call is unknown but after
the test, n is in the range [0, 7].  In the second call to snprintf the amount
of space remaining at &a[n] is in the range [1, 8].  Level 1 of the warning
optimistically uses the upper bound of the range and assumes there's still 8
bytes worth of space.  But the conservative level 2 uses the lower bound and
assumes there's just 1 byte left (i.e., n == 7).  Hence the warning.

The warning is justified because the code really isn't 100% safe from
truncation.  It's working as designed and documented:

  Level 2 warns also about calls to bounded functions whose return value is
used and that might result in truncation given an argument of sufficient length
or magnitude. 

$ cat t.c && gcc -O2 -S -Wall -Wformat-truncation=2 t.c
char a[8];

void f (char const *s)
{
  int n = __builtin_snprintf (a, sizeof a, "%s", s);

  if (n < 0 || n >= sizeof a)
    __builtin_abort ();

  unsigned r = sizeof a - n;

  n = __builtin_snprintf (&a[n], r, "12");

  if (n < 0 || n >= sizeof a)
    __builtin_abort ();
}

t.c: In function ‘f’:
t.c:12:39: warning: ‘12’ directive output truncated writing 2 bytes into a
region of size 1 [-Wformat-truncation=]
   12 |   n = __builtin_snprintf (&a[n], r, "12");
      |                                      ~^
t.c:12:7: note: ‘__builtin_snprintf’ output 3 bytes into a destination of size
1
   12 |   n = __builtin_snprintf (&a[n], r, "12");
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


I forgot that level 2 doesn't consider whether the return value is used.  It
might perhaps be worth reconsidering whether the warning should trigger when
the return value is used at level 2.

But even if this change were made, at level 2 not all false positives can be
avoided.  For example, the warning pass isn't integrated with the string length
pass, so even though GCC might be able to track the lengths of some dynamically
created strings the warning doesn't know about them.  For instance, in the
following GCC eliminates the test because it knows that strlen(b) is 3 but the
warning still triggers.  We plan to make this work better in GCC 10.

$ cat t.c && gcc -O2 -S -Wall -Wformat-truncation=2 t.c
char a[8];

void f (void)
{
  char b[10];
  __builtin_strcpy (b, "123");

  if (__builtin_strlen (b) > 3)
    return;

  __builtin_snprintf (a, sizeof a, "%s", b);
}

t.c: In function ‘f’:
t.c:11:37: warning: ‘%s’ directive output may be truncated writing up to 9
bytes into a region of size 8 [-Wformat-truncation=]
   11 |   __builtin_snprintf (a, sizeof a, "%s", b);
      |                                     ^~   ~
t.c:11:3: note: ‘__builtin_snprintf’ output between 1 and 10 bytes into a
destination of size 8
   11 |   __builtin_snprintf (a, sizeof a, "%s", b);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Reply via email to