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

--- Comment #3 from Mark Wielaard <mark at gcc dot gnu.org> ---
A "workaround" for the example given in the description is to just pick some
arbitrary number you know wouldn't get exceeded. e.g:

  /* To help -Wformat-truncation=2 pretend the "count"
     translation will never be bigger than 128 chars.  */
  if (snprintf (buf, len, "%.*s: %d", 128 gettext ("count"), count) >= len)
    return NULL;

But I have also some code that explicitly wants to know whether or not the size
of the formatted string will exceed what is currently available, to make sure
the output buffer gets resized. e.g.

  /* Would the output fit in the current buffer?  */
  int needed = snprintf (buf, avail, "%%mm%" PRIxFAST8, byte);
  if (needed > avail)
    ...

There is no good workaround for the second case.

Both cases seem to be solved by the proposed patch. While it still catches the
real issue also found by -Wformat-truncation=2.

But... The following produces different output depending on whether the code is
compiled with -Wformat-truncation=2 or not (-Wformat-truncation=1 also does
produce the expected output).

The idea of the code is that we snprintf into a buffer at a particular index
that is updated depending on the size of the string produced or, if the buffer
isn't big enough, returns extra space that would be needed.

This is using gcc trunk and the posted patch.

$ cat fct.c 
#include <stdio.h>
#include <string.h>
#include <inttypes.h>

struct output_data
{
  char *bufp;           // buffer to write in.
  size_t *bufcntp;      // where in the buffer to write (to be updated).
  size_t bufsize;       // total buffer size.
  const uint8_t *data;  // auxiliary data (which register, flags, etc).
};

static int
FCT_freg (struct output_data *d)
{
  size_t *bufcntp = d->bufcntp;
  size_t avail = d->bufsize - *bufcntp;
  int needed = snprintf (&d->bufp[*bufcntp], avail, "%%st(%" PRIx32 ")",
                         (uint32_t) (d->data[1] & 7));
  if ((size_t) needed > avail)
    return (size_t) needed - avail;
  *bufcntp += needed;
  return 0;
}

typedef int (*opfct_t) (struct output_data *);
static const opfct_t op1_fct = FCT_freg;

int
main (int argc, char **argv)
{
  size_t size = 64;
  char buf[size];
  size_t idx = 0;
  uint8_t data[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
  int res;
  const char *prefix = "regs: ";

  struct output_data output_data =
    {
      .bufp = buf,
      .bufsize = size,
      .bufcntp = &idx,
      .data = data,
    };

  // Add some random data to the buf for debugging.
  memset (buf, 'A', size);
  buf[size - 1] = '\0';

  memcpy (buf, prefix, strlen (prefix) + 1);
  idx = strlen (prefix);
  for (uint8_t i = 0; i < 3; i++)
    {
      data[1] = i; // Update register number to print.
      buf[idx] = 'X'; // Override expected zero char for debugging.

      printf ("Adding to buf at idx: %zd\n", idx);

      res = op1_fct (&output_data);
      if (res == 0)
        {
          printf ("buf: \"%s\"\n", buf);
          buf[idx++] = ',';
        }
      else
        printf ("Need more space: %d\n", res);
    }
  return 0;
}

$ gcc -g -O2 -o fct fct.c
$ ./fct 
Adding to buf at idx: 6
buf: "regs: %st(0)"
Adding to buf at idx: 13
buf: "regs: %st(0),%st(1)"
Adding to buf at idx: 20
buf: "regs: %st(0),%st(1),%st(2)"

$ gcc -Wformat-truncation=2 -g -O2 -o fct fct.c 
$ ./fct 
Adding to buf at idx: 6
buf: "regs: XAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Adding to buf at idx: 13
buf: "regs: XAAAAA,XAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Adding to buf at idx: 20
buf: "regs: XAAAAA,XAAAAA,XAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"

Note how with -Wformat-truncation=2 the idx is correctly updated, but the
characters aren't actually written into the buffer...

Dropping -O2 does produce the expected output again.

Reply via email to