PR tree-optimization/79327 - wrong code at -O2 and -fprintf-return-value
authorMartin Sebor <msebor@redhat.com>
Fri, 3 Feb 2017 22:47:35 +0000 (22:47 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Fri, 3 Feb 2017 22:47:35 +0000 (15:47 -0700)
PR tree-optimization/79327 - wrong code at -O2 and -fprintf-return-value
gcc/ChangeLog:
* gimple-ssa-sprintf.c (tree_digits): Avoid adding the base prefix
when precision has resulted in leading zeros.
(format_integer): Adjust the likely counter to assume an unknown
argument that may be zero is non-zero.

gcc/testsuite/ChangeLog:
* gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Adjust.
* gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-13.c: New test.
* gcc/testsuite/gcc.dg/tree-ssa/pr79327-2.c: Ditto.

From-SVN: r245173

gcc/ChangeLog
gcc/gimple-ssa-sprintf.c
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-13.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/pr79327-2.c [new file with mode: 0644]

index 044f720e9908ea89eaeb5caa2c9a7eab11da77a1..40993e5a0dd4cb9dd5eac13304734e4a7407db96 100644 (file)
@@ -1,3 +1,11 @@
+2017-02-03  Martin Sebor  <msebor@redhat.com>
+
+       PR tree-optimization/79327
+       * gimple-ssa-sprintf.c (tree_digits): Avoid adding the base prefix
+       when precision has resulted in leading zeros.
+       (format_integer): Adjust the likely counter to assume an unknown
+       argument that may be zero is non-zero.
+
 2017-02-03  Jason Merrill  <jason@redhat.com>
 
        PR c++/78689
index ac4e959ed3c1114cab542e212b32f4dd719420bb..e6cc31d1c4831ee49b40b0b4b7609eecc90f9ce0 100644 (file)
@@ -762,7 +762,9 @@ tree_digits (tree x, int base, HOST_WIDE_INT prec, bool plus, bool prefix)
 
   res += prec < ndigs ? ndigs : prec;
 
-  if (prefix && absval)
+  /* Adjust a non-zero value for the base prefix, either hexadecimal,
+     or, unless precision has resulted in a leading zero, also octal.  */
+  if (prefix && absval && (base == 16 || prec <= ndigs))
     {
       if (base == 8)
        res += 1;
@@ -1230,6 +1232,10 @@ format_integer (const directive &dir, tree arg)
        of the format string by returning [-1, -1].  */
     return fmtresult ();
 
+  /* True if the LIKELY counter should be adjusted upward from the MIN
+     counter to account for arguments with unknown values.  */
+  bool likely_adjust = false;
+
   fmtresult res;
 
   /* Using either the range the non-constant argument is in, or its
@@ -1259,6 +1265,14 @@ format_integer (const directive &dir, tree arg)
 
          res.argmin = argmin;
          res.argmax = argmax;
+
+         /* Set the adjustment for an argument whose range includes
+            zero since that doesn't include the octal or hexadecimal
+            base prefix.  */
+         wide_int wzero = wi::zero (wi::get_precision (min));
+         if (wi::le_p (min, wzero, SIGNED)
+             && !wi::neg_p (max))
+           likely_adjust = true;
        }
       else if (range_type == VR_ANTI_RANGE)
        {
@@ -1293,6 +1307,11 @@ format_integer (const directive &dir, tree arg)
 
   if (!argmin)
     {
+      /* Set the adjustment for an argument whose range includes
+        zero since that doesn't include the octal or hexadecimal
+        base prefix.  */
+      likely_adjust = true;
+
       if (TREE_CODE (argtype) == POINTER_TYPE)
        {
          argmin = build_int_cst (pointer_sized_int_node, 0);
@@ -1345,7 +1364,24 @@ format_integer (const directive &dir, tree arg)
       res.range.max = MAX (max1, max2);
     }
 
-  res.range.likely = res.knownrange ? res.range.max : res.range.min;
+  /* Add the adjustment for an argument whose range includes zero
+     since it doesn't include the octal or hexadecimal base prefix.  */
+  if (res.knownrange)
+    res.range.likely = res.range.max;
+  else
+    {
+      res.range.likely = res.range.min;
+      if (likely_adjust && maybebase && base != 10)
+       {
+         if (res.range.min == 1)
+           res.range.likely += base == 8 ? 1 : 2;
+         else if (res.range.min == 2
+                  && base == 16
+                  && (dir.width[0] == 2 || dir.prec[0] == 2))
+           ++res.range.likely;
+       }
+    }
+
   res.range.unlikely = res.range.max;
   res.adjust_for_width_or_precision (dir.width, dirtype, base,
                                     (sign | maybebase) + (base == 16));
index 7a34cfeb6dba5d5e99ccec86b0598ddb9d71d306..73696e5b6cc2451d58a9f633016503e98b118c98 100644 (file)
@@ -1,3 +1,10 @@
+2017-02-03  Martin Sebor  <msebor@redhat.com>
+
+       PR tree-optimization/79327
+       * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Adjust.
+       * gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-13.c: New test.
+       * gcc/testsuite/gcc.dg/tree-ssa/pr79327-2.c: Ditto.
+
 2017-02-03  Jakub Jelinek  <jakub@redhat.com>
            Martin Sebor  <msebor@redhat.com>
 
index fef9578921d07af4588e6a80f4847674a9d46bdd..b4a9a6e61b629e40ccaf5ccdc9592226fd2cc78e 100644 (file)
@@ -1151,8 +1151,16 @@ void test_sprintf_chk_hh_nonconst (int w, int p, int a)
   T (2, "% hhu",        a);     /* { dg-warning ". . flag used with .%u." } */
   T (2, "% hhx",        a);     /* { dg-warning ". . flag used with .%x." } */
 
-  T (2, "%#hho",        a);
-  T (2, "%#hhx",        a);
+  /* The following results in between "0" and "0377" for -1.  Although
+     the minimum output would fit, given the '#' flag the likely output
+     (i.e., for any non-zero argument) includes a leading zero followed
+     by one or more octal digits, which results in the terminating nul
+     being written past the end.  Thus the "may write" warning.  */
+  T (2, "%#hho",        a);     /* { dg-warning "may write a terminating nul" } */
+  /* Similar to the above, but the likely output of the directive for
+     a non-zero argument overflows.  Thus the "writing X bytes" (as
+     opposed to "may write") warning.  */
+  T (2, "%#hhx",        a);     /* { dg-warning "writing between 1 and 4 bytes" } */
 
   T (3, "%0hhd",        a);
   T (3, "%1hhd",        a);
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-13.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-13.c
new file mode 100644 (file)
index 0000000..27a9972
--- /dev/null
@@ -0,0 +1,261 @@
+/* { dg-do compile }
+   { dg-options "-O2 -Wall -Wformat-overflow=1 -ftrack-macro-expansion=0" }
+   { dg-require-effective-target int32plus } */
+
+typedef __SIZE_TYPE__  size_t;
+typedef __WCHAR_TYPE__ wchar_t;
+
+#define INT_MAX __INT_MAX__
+#define INT_MIN (-INT_MAX - 1)
+
+/* When debugging, define LINE to the line number of the test case to exercise
+   and avoid exercising any of the others.  The buffer and objsize macros
+   below make use of LINE to avoid warnings for other lines.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+void sink (char*, char*);
+
+int dummy_sprintf (char*, const char*, ...);
+
+char buffer [256];
+extern char *ptr;
+
+int int_range (int min, int max)
+{
+  extern int int_value (void);
+  int n = int_value ();
+  return n < min || max < n ? min : n;
+}
+
+unsigned uint_range (unsigned min, unsigned max)
+{
+  extern unsigned uint_value (void);
+  unsigned n = uint_value ();
+  return n < min || max < n ? min : n;
+}
+
+/* Evaluate to an array of SIZE characters when non-negative, or to
+   a pointer to an unknown object otherwise.  */
+#define buffer(size)                                   \
+  ((0 <= size) ? buffer + sizeof buffer - (size) : ptr)
+
+/* Helper to expand function to either __builtin_f or dummy_f to
+   make debugging GCC easy.  */
+#define FUNC(f)                                                        \
+  ((!LINE || LINE == __LINE__) ? __builtin_ ## f : dummy_ ## f)
+
+/* Macro to verify that calls to __builtin_sprintf (i.e., with no size
+   argument) issue diagnostics by correctly determining the size of
+   the destination buffer.  */
+#define T(size, ...)                                           \
+  (FUNC (sprintf) (buffer (size),  __VA_ARGS__),               \
+   sink (buffer, ptr))
+
+/* Return a signed integer in the range [MIN, MAX].  */
+#define R(min, max)  int_range (min, max)
+
+/* Verify warnings and ranges for certain overflow.  */
+void test_min_overflow (int i)
+{
+  T (0, "%#hho", i);            /* { dg-warning "between 1 and 4 bytes" } */
+  T (0, "%#1hho", i);           /* { dg-warning "between 1 and 4 bytes" } */
+  T (0, "%#2hho", i);           /* { dg-warning "between 2 and 4 bytes" } */
+  T (0, "%#3hho", i);           /* { dg-warning "between 3 and 4 bytes" } */
+  T (0, "%#4hho", i);           /* { dg-warning "writing 4 bytes" } */
+  T (0, "%#hho", R (-1,  0));   /* { dg-warning "between 1 and 4 bytes" } */
+  T (0, "%#1hho", R (-1,  0));  /* { dg-warning "between 1 and 4 bytes" } */
+  T (0, "%#2hho", R (-1,  0));  /* { dg-warning "between 2 and 4 bytes" } */
+  T (0, "%#3hho", R (-1,  0));  /* { dg-warning "between 3 and 4 bytes" } */
+  T (0, "%#4hho", R (-1,  0));  /* { dg-warning "writing 4 bytes" } */
+  T (0, "%#hho", R (-1,  1));   /* { dg-warning "between 1 and 4 bytes" } */
+  T (0, "%#1hho", R (-1,  1));  /* { dg-warning "between 1 and 4 bytes" } */
+  T (0, "%#2hho", R (-1,  1));  /* { dg-warning "between 2 and 4 bytes" } */
+  T (0, "%#3hho", R (-1,  1));  /* { dg-warning "between 3 and 4 bytes" } */
+  T (0, "%#4hho", R (-1,  1));  /* { dg-warning "writing 4 bytes" } */
+  T (0, "%#hho", R ( 0,  1));   /* { dg-warning "between 1 and 2 bytes" } */
+  T (0, "%#1hho", R ( 0,  1));  /* { dg-warning "between 1 and 2 bytes" } */
+  T (0, "%#2hho", R ( 0,  1));  /* { dg-warning "writing 2 bytes" } */
+  T (0, "%#3hho", R ( 0,  1));  /* { dg-warning "writing 3 bytes" } */
+  T (0, "%#4hho", R ( 0,  1));  /* { dg-warning "writing 4 bytes" } */
+  T (0, "%#hho", R ( 1,  2));   /* { dg-warning "writing 2 bytes" } */
+  T (0, "%#1hho", R ( 1,  2));  /* { dg-warning "writing 2 bytes" } */
+  T (0, "%#2hho", R ( 1,  2));  /* { dg-warning "writing 2 bytes" } */
+  T (0, "%#3hho", R ( 1,  2));  /* { dg-warning "writing 3 bytes" } */
+  T (0, "%#4hho", R ( 1,  2));  /* { dg-warning "writing 4 bytes" } */
+
+  T (0, "%#ho",  i);            /* { dg-warning "between 1 and 7 bytes" } */
+  T (0, "%#.*ho",               /* { dg-warning "between 1 and 7 bytes" } */
+     R (0, 2), i);
+  T (0, "%#.*ho",               /* { dg-warning "between 1 and 7 bytes" } */
+     R (1, 2), i);
+  T (0, "%#.*ho",               /* { dg-warning "between 2 and 7 bytes" } */
+     R (2, 3), i);
+  T (0, "%#.*ho",               /* { dg-warning "between 3 and 7 bytes" } */
+     R (3, 4), i);
+  T (0, "%#.*ho",               /* { dg-warning "between 7 and 8 bytes" } */
+     R (7, 8), i);
+
+  T (0, "%#ho",  R (-1,  0));   /* { dg-warning "between 1 and 7 bytes" } */
+  T (0, "%#ho",  R (-1,  1));   /* { dg-warning "between 1 and 7 bytes" } */
+  T (0, "%#ho",  R ( 0,  1));   /* { dg-warning "between 1 and 2 bytes" } */
+  T (0, "%#ho",  R ( 1,  2));   /* { dg-warning "writing 2 bytes" } */
+
+  T (0, "%#o",   i);            /* { dg-warning "between 1 and 12 bytes" } */
+  T (0, "%#o",   R (-1,  0));   /* { dg-warning "between 1 and 12 bytes" } */
+  T (0, "%#o",   R (-1,  1));   /* { dg-warning "between 1 and 12 bytes" } */
+  T (0, "%#o",   R ( 0,  1));   /* { dg-warning "between 1 and 2 bytes" } */
+  T (0, "%#o",   R ( 1,  2));   /* { dg-warning "writing 2 bytes" } */
+
+  T (0, "%#hhx", i);            /* { dg-warning "between 1 and 4 bytes" } */
+  T (0, "%#.*hhx",              /* { dg-warning "writing up to 4 bytes" } */
+     R (0, 2), i);
+  T (0, "%#.*hhx",              /* { dg-warning "between 1 and 4 bytes" } */
+     R (1, 2), i);
+  T (0, "%#.*hhx",              /* { dg-warning "between 2 and 5 bytes" } */
+     R (2, 3), i);
+  T (0, "%#.*hhx",              /* { dg-warning "between 3 and 6 bytes" } */
+     R (3, 4), i);
+
+  T (0, "%#hhx", R (-1,  0));   /* { dg-warning "between 1 and 4 bytes" } */
+  T (0, "%#hhx", R (-1,  1));   /* { dg-warning "between 1 and 4 bytes" } */
+  T (0, "%#hhx", R ( 0,  1));   /* { dg-warning "between 1 and 3 bytes" } */
+  T (0, "%#hhx", R ( 1,  2));   /* { dg-warning "writing 3 bytes" } */
+
+  T (0, "%#hx", i);             /* { dg-warning "between 1 and 6 bytes" } */
+  T (0, "%#hx", R (-1,  0));    /* { dg-warning "between 1 and 6 bytes" } */
+  T (0, "%#hx", R (-1,  1));    /* { dg-warning "between 1 and 6 bytes" } */
+  T (0, "%#hx", R ( 0,  1));    /* { dg-warning "between 1 and 3 bytes" } */
+  T (0, "%#hx", R ( 1,  2));    /* { dg-warning "writing 3 bytes" } */
+
+  T (0, "%#x",   i);            /* { dg-warning "between 1 and 10 bytes" } */
+  T (0, "%#x",   R (-1,  0));   /* { dg-warning "between 1 and 10 bytes" } */
+  T (0, "%#x",   R (-1,  1));   /* { dg-warning "between 1 and 10 bytes" } */
+  T (0, "%#x",   R ( 0,  1));   /* { dg-warning "between 1 and 3 bytes" } */
+  T (0, "%#x",   R ( 1,  2));   /* { dg-warning "writing 3 bytes" } */
+}
+
+/* Verify warnings and ranges for likely overflow.  */
+void test_likely_overflow (int i)
+{
+  T (2, "%#hho", i);          /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#1hho", i);         /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#2hho", i);         /* { dg-warning "writing a terminating nul" } */
+  T (2, "%#3hho", i);         /* { dg-warning "between 3 and 4 bytes" } */
+  T (2, "%#4hho", i);         /* { dg-warning "writing 4 bytes" } */
+  T (2, "%#hho", R (-1,  0)); /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#1hho", R (-1,  0));/* { dg-warning "may write a terminating nul" } */
+  T (2, "%#2hho", R (-1,  0));/* { dg-warning "writing a terminating nul" } */
+  T (2, "%#3hho", R (-1,  0));/* { dg-warning "between 3 and 4 bytes" } */
+  T (2, "%#4hho", R (-1,  0));/* { dg-warning "writing 4 bytes" } */
+  T (2, "%#hho", R (-1,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#1hho", R (-1,  1));/* { dg-warning "may write a terminating nul" } */
+  T (2, "%#2hho", R (-1,  1));/* { dg-warning "writing a terminating nul" } */
+  T (2, "%#3hho", R (-1,  1));/* { dg-warning "between 3 and 4 bytes" } */
+  T (2, "%#4hho", R (-1,  1));/* { dg-warning "writing 4 bytes" } */
+  T (2, "%#hho", R ( 0,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#1hho", R ( 0,  1));/* { dg-warning "may write a terminating nul" } */
+  T (2, "%#2hho", R ( 0,  1));/* { dg-warning "writing a terminating nul" } */
+  T (2, "%#3hho", R ( 0,  1));/* { dg-warning "writing 3 bytes" } */
+  T (2, "%#4hho", R ( 0,  1));/* { dg-warning "writing 4 bytes" } */
+  T (2, "%#hho", R ( 1,  2)); /* { dg-warning "writing a terminating nul" } */
+  T (2, "%#1hho", R ( 1,  2));/* { dg-warning "writing a terminating nul" } */
+  T (2, "%#2hho", R ( 1,  2));/* { dg-warning "writing a terminating nul" } */
+  T (2, "%#3hho", R ( 1,  2));/* { dg-warning "writing 3 bytes" } */
+  T (2, "%#4hho", R ( 1,  2));/* { dg-warning "writing 4 bytes" } */
+
+  T (2, "%#ho",  i);          /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#ho",  R (-1,  0)); /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#ho",  R (-1,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#ho",  R ( 0,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#ho",  R ( 1,  2)); /* { dg-warning "writing a terminating nul" } */
+
+  T (2, "%#o",   i);          /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#o",   R (-1,  0)); /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#o",   R (-1,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#o",   R ( 0,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (2, "%#o",   R ( 1,  2)); /* { dg-warning "writing a terminating nul" } */
+
+  T (2, "%#hhx", i);          /* { dg-warning "between 1 and 4 bytes" } */
+  T (2, "%#1hhx", i);         /* { dg-warning "between 1 and 4 bytes" } */
+  T (2, "%#2hhx", i);         /* { dg-warning "between 2 and 4 bytes" } */
+  T (2, "%#3hhx", i);         /* { dg-warning "between 3 and 4 bytes" } */
+  T (2, "%#4hhx", i);         /* { dg-warning "writing 4 bytes" } */
+  T (2, "%#1hhx", R (-1,  0));/* { dg-warning "between 1 and 4 bytes" } */
+  T (2, "%#2hhx", R (-1,  0));/* { dg-warning "between 2 and 4 bytes" } */
+  T (2, "%#3hhx", R (-1,  0));/* { dg-warning "between 3 and 4 bytes" } */
+  T (2, "%#4hhx", R (-1,  0));/* { dg-warning "writing 4 bytes" } */
+  T (2, "%#hhx", R (-1,  0)); /* { dg-warning "between 1 and 4 bytes" } */
+  T (2, "%#1hhx", R (-1,  0));/* { dg-warning "between 1 and 4 bytes" } */
+  T (2, "%#2hhx", R (-1,  0));/* { dg-warning "between 2 and 4 bytes" } */
+  T (2, "%#3hhx", R (-1,  0));/* { dg-warning "between 3 and 4 bytes" } */
+  T (2, "%#4hhx", R (-1,  0));/* { dg-warning "writing 4 bytes" } */
+  T (2, "%#hhx", R (-1,  1)); /* { dg-warning "between 1 and 4 bytes" } */
+  T (2, "%#1hhx", R (-1,  1));/* { dg-warning "between 1 and 4 bytes" } */
+  T (2, "%#2hhx", R (-1,  1));/* { dg-warning "between 2 and 4 bytes" } */
+  T (2, "%#3hhx", R (-1,  1));/* { dg-warning "between 3 and 4 bytes" } */
+  T (2, "%#4hhx", R (-1,  1));/* { dg-warning "writing 4 bytes" } */
+  T (2, "%#hhx", R ( 0,  1)); /* { dg-warning "between 1 and 3 bytes" } */
+  T (2, "%#1hhx", R ( 0,  1));/* { dg-warning "between 1 and 3 bytes" } */
+  T (2, "%#2hhx", R ( 0,  1));/* { dg-warning "between 2 and 3 bytes" } */
+  T (2, "%#3hhx", R ( 0,  1));/* { dg-warning "writing 3 bytes" } */
+  T (2, "%#4hhx", R ( 0,  1));/* { dg-warning "writing 4 bytes" } */
+  T (2, "%#hhx", R ( 1,  2)); /* { dg-warning "writing 3 bytes" } */
+  T (2, "%#1hhx", R ( 1,  2));/* { dg-warning "writing 3 bytes" } */
+  T (2, "%#2hhx", R ( 1,  2));/* { dg-warning "writing 3 bytes" } */
+  T (2, "%#3hhx", R ( 1,  2));/* { dg-warning "writing 3 bytes" } */
+  T (2, "%#4hhx", R ( 1,  2));/* { dg-warning "writing 4 bytes" } */
+
+  T (2, "%#hx", i);           /* { dg-warning "between 1 and 6 bytes" } */
+  T (2, "%#hx", R (-1,  0));  /* { dg-warning "between 1 and 6 bytes" } */
+  T (2, "%#hx", R (-1,  1));  /* { dg-warning "between 1 and 6 bytes" } */
+  T (2, "%#hx", R ( 0,  1));  /* { dg-warning "between 1 and 3 bytes" } */
+  T (2, "%#hx", R ( 1,  2));  /* { dg-warning "writing 3 bytes" } */
+
+  T (2, "%#x",   i);          /* { dg-warning "between 1 and 10 bytes" } */
+  T (2, "%#x",   R (-1,  0)); /* { dg-warning "between 1 and 10 bytes" } */
+  T (2, "%#x",   R (-1,  1)); /* { dg-warning "between 1 and 10 bytes" } */
+  T (2, "%#x",   R ( 0,  1)); /* { dg-warning "between 1 and 3 bytes" } */
+  T (2, "%#x",   R ( 1,  2)); /* { dg-warning "writing 3 bytes" } */
+}
+
+/* Verify warnings likely overflow due to the terminating nul.  */
+void test_likely_nul_overflow (int i)
+{
+  T (3, "%#hho", i);
+  T (3, "%#hho", R (-1,  0));
+  T (3, "%#hho", R (-1,  1));
+  T (3, "%#hho", R ( 0,  1));
+  T (3, "%#hho", R ( 1,  2));
+
+  T (3, "%#ho",  i);
+  T (3, "%#ho",  R (-1,  0));
+  T (3, "%#ho",  R (-1,  1));
+  T (3, "%#ho",  R ( 0,  1));
+  T (3, "%#ho",  R ( 1,  2));
+
+  T (3, "%#o",   i);
+  T (3, "%#o",   R (-1,  0));
+  T (3, "%#o",   R (-1,  1));
+  T (3, "%#o",   R ( 0,  1));
+  T (3, "%#o",   R ( 1,  2));
+
+  T (3, "%#hhx", i);          /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#hhx", R (-1,  0)); /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#hhx", R (-1,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#hhx", R ( 0,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#hhx", R ( 1,  2)); /* { dg-warning "writing a terminating nul" } */
+
+  T (3, "%#hx", i);           /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#hx", R (-1,  0));  /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#hx", R (-1,  1));  /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#hx", R ( 0,  1));  /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#hx", R ( 1,  2));  /* { dg-warning "writing a terminating nul" } */
+
+  T (3, "%#x",   i);          /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#x",   R (-1,  0)); /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#x",   R (-1,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#x",   R ( 0,  1)); /* { dg-warning "may write a terminating nul" } */
+  T (3, "%#x",   R ( 1,  2)); /* { dg-warning "writing a terminating nul" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr79327-2.c b/gcc/testsuite/gcc.dg/tree-ssa/pr79327-2.c
new file mode 100644 (file)
index 0000000..f72e4cc
--- /dev/null
@@ -0,0 +1,159 @@
+/* PR tree-optimization/79327 - wrong code at -O2 and -fprintf-return-value
+   { dg-compile }
+   { dg-options "-O2 -fdump-tree-optimized" } */
+
+#define CAT(s, n)   s ## n
+#define FAIL(line)  CAT (failure_on_line_, line)
+
+/* Emit a call to a function named failure_on_line_NNN when EXPR is false.  */
+#define ASSERT(expr)                           \
+  do {                                         \
+    extern void FAIL (__LINE__)(void);         \
+    if (!(expr)) FAIL (__LINE__)();            \
+  } while (0)
+
+#define KEEP(line)  CAT (keep_call_on_line_, line)
+
+/* Emit a call to a function named keep_call_on_line_NNN when EXPR is true.
+   Used to verify that the expression need not be the only one that holds.  */
+#define ASSERT_MAYBE(expr)                     \
+  do {                                         \
+    extern void KEEP (__LINE__)(void);         \
+    if (expr) KEEP (__LINE__)();               \
+  } while (0)
+
+void test_hho_cst (void)
+{
+  ASSERT (1 == __builtin_snprintf (0, 0, "%#.1hho", 0));
+  ASSERT (2 == __builtin_snprintf (0, 0, "%#.2hho", 0));
+
+  ASSERT (2 == __builtin_snprintf (0, 0, "%#.1hho", 1));
+  ASSERT (2 == __builtin_snprintf (0, 0, "%#.2hho", 1));
+  ASSERT (3 == __builtin_snprintf (0, 0, "%#.3hho", 1));
+}
+
+int test_hho_var (int i)
+{
+  int n = __builtin_snprintf (0, 0, "%#hho", i);
+
+  ASSERT (0 < n && n < 5);
+
+  ASSERT_MAYBE (1 == n);
+  ASSERT_MAYBE (2 == n);
+  ASSERT_MAYBE (3 == n);
+  ASSERT_MAYBE (4 == n);
+
+  return n;
+}
+
+int test_ho_var (int i)
+{
+  int n = __builtin_snprintf (0, 0, "%#ho", i);
+
+  ASSERT (0 < n && n < 8);
+
+  ASSERT_MAYBE (1 == n);
+  ASSERT_MAYBE (2 == n);
+  ASSERT_MAYBE (3 == n);
+  ASSERT_MAYBE (4 == n);
+  ASSERT_MAYBE (5 == n);
+  ASSERT_MAYBE (6 == n);
+  ASSERT_MAYBE (7 == n);
+
+  return n;
+}
+
+int test_o_var (int i)
+{
+  int n = __builtin_snprintf (0, 0, "%#o", i);
+
+  ASSERT (0 < n && n < 13);
+
+  ASSERT_MAYBE (1 == n);
+  ASSERT_MAYBE (2 == n);
+  ASSERT_MAYBE (3 == n);
+  ASSERT_MAYBE (4 == n);
+  ASSERT_MAYBE (5 == n);
+  ASSERT_MAYBE (6 == n);
+  ASSERT_MAYBE (7 == n);
+  ASSERT_MAYBE (8 == n);
+  ASSERT_MAYBE (9 == n);
+  ASSERT_MAYBE (10 == n);
+  ASSERT_MAYBE (11 == n);
+  ASSERT_MAYBE (12 == n);
+
+  return n;
+}
+
+void test_hhx_cst (void)
+{
+  ASSERT (1 == __builtin_snprintf (0, 0, "%#.1hhx", 0));
+  ASSERT (2 == __builtin_snprintf (0, 0, "%#.2hhx", 0));
+
+  ASSERT (3 == __builtin_snprintf (0, 0, "%#.1hhx", 1));
+  ASSERT (4 == __builtin_snprintf (0, 0, "%#.2hhx", 1));
+  ASSERT (5 == __builtin_snprintf (0, 0, "%#.3hhx", 1));
+}
+
+int test_hhx_var (int i)
+{
+  int n = __builtin_snprintf (0, 0, "%#hhx", i);
+
+  ASSERT (0 < n && n < 5);
+
+  ASSERT_MAYBE (1 == n);
+  ASSERT_MAYBE (2 == n);
+  ASSERT_MAYBE (3 == n);
+  ASSERT_MAYBE (4 == n);
+
+  return n;
+}
+
+void test_hx_cst (void)
+{
+  ASSERT (1 == __builtin_snprintf (0, 0, "%#.1hx", 0));
+  ASSERT (2 == __builtin_snprintf (0, 0, "%#.2hx", 0));
+
+  ASSERT (3 == __builtin_snprintf (0, 0, "%#.1hx", 1));
+  ASSERT (4 == __builtin_snprintf (0, 0, "%#.2hx", 1));
+  ASSERT (5 == __builtin_snprintf (0, 0, "%#.3hx", 1));
+}
+
+int test_hx_var (int i)
+{
+  int n = __builtin_snprintf (0, 0, "%#hx", i);
+
+  ASSERT (0 < n && n < 7);
+
+  ASSERT_MAYBE (1 == n);
+  ASSERT_MAYBE (2 == n);
+  ASSERT_MAYBE (3 == n);
+  ASSERT_MAYBE (4 == n);
+  ASSERT_MAYBE (5 == n);
+  ASSERT_MAYBE (6 == n);
+
+  return n;
+}
+
+int test_x_var (int i)
+{
+  int n = __builtin_snprintf (0, 0, "%#x", i);
+
+  ASSERT (0 < n && n < 11);
+
+  ASSERT_MAYBE (1 == n);
+  ASSERT_MAYBE (2 == n);
+  ASSERT_MAYBE (3 == n);
+  ASSERT_MAYBE (4 == n);
+  ASSERT_MAYBE (5 == n);
+  ASSERT_MAYBE (6 == n);
+  ASSERT_MAYBE (7 == n);
+  ASSERT_MAYBE (8 == n);
+  ASSERT_MAYBE (9 == n);
+  ASSERT_MAYBE (10 == n);
+
+  return n;
+}
+
+/* { dg-final { scan-tree-dump-not "failure_on_line" "optimized"} }
+   { dg-final { scan-tree-dump-times "keep_call_on_line" 43 "optimized"} } */