PR middle-end/79448 - unhelpful -Wformat-truncation=2 warning

gcc/testsuite/ChangeLog:

	PR middle-end/79448
	* gcc.dg/tree-ssa/builtin-snprintf-warn-3.c: New test.
	* gcc.dg/tree-ssa/pr79448-2.c: New test.
	* gcc.dg/tree-ssa/pr79448.c: New test.

gcc/ChangeLog:

	PR middle-end/79448
	* gimple-ssa-sprintf.c (format_directive): Avoid issuing INT_MAX
	  warning for strings of unknown length.

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index e6cc31d..bf76162 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -2561,11 +2561,15 @@ format_directive (const pass_sprintf_length::call_info &info,
   /* Raise the total unlikely maximum by the larger of the maximum
      and the unlikely maximum.  It doesn't matter if the unlikely
      maximum overflows.  */
+  unsigned HOST_WIDE_INT save = res->range.unlikely;
   if (fmtres.range.max < fmtres.range.unlikely)
     res->range.unlikely += fmtres.range.unlikely;
   else
     res->range.unlikely += fmtres.range.max;
 
+  if (res->range.unlikely < save)
+    res->range.unlikely = HOST_WIDE_INT_M1U;
+
   res->range.min += fmtres.range.min;
   res->range.likely += fmtres.range.likely;
 
@@ -2616,7 +2620,12 @@ format_directive (const pass_sprintf_length::call_info &info,
 
   /* Has the likely and maximum directive output exceeded INT_MAX?  */
   bool likelyximax = *dir.beg && res->range.likely > target_int_max ();
-  bool maxximax = *dir.beg && res->range.max > target_int_max ();
+  /* Don't consider the maximum to be in excess when it's the result
+     of a string of unknown length (i.e., whose maximum has been set
+     to HOST_WIDE_INT_M1U.  */
+  bool maxximax = (*dir.beg
+		   && res->range.max > target_int_max ()
+		   && res->range.max < HOST_WIDE_INT_MAX);
 
   if (!warned
       /* Warn for the likely output size at level 1.  */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-3.c
new file mode 100644
index 0000000..81c1d89
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-3.c
@@ -0,0 +1,193 @@
+/* PR middle-end/79448 - unhelpful -Wformat-truncation=2 warning
+   { dg-do compile }
+   { dg-options "-O2 -Wformat -Wformat-truncation=2 -ftrack-macro-expansion=0" } */
+
+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
+
+extern int int_value (void);
+extern size_t size_value (void);
+
+int int_range (int min, int max)
+{
+  int n = int_value ();
+  return n < min || max < n ? min : n;
+}
+
+void sink (int, char*, char*);
+
+int dummy_snprintf (char*, size_t, const char*, ...);
+
+char fixed_buffer [256];
+extern char *unknown_buffer;
+extern size_t unknown_size;
+
+/* 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)
+
+/* Helper test macro.  */
+#define T(size, ...)					\
+  do {							\
+    size_t n = size < 0 ? unknown_size : size;		\
+    char *buf = size < 0 ? unknown_buffer		\
+      : n < sizeof fixed_buffer				\
+      ? fixed_buffer + sizeof fixed_buffer - size	\
+      : unknown_buffer;					\
+    FUNC (snprintf) (buf, n, __VA_ARGS__);		\
+    sink (0, fixed_buffer, unknown_buffer);		\
+  } while (0)
+
+/* Return a value in the range [MIN, MAX].  */
+#define IR(min, max)  int_range (min, max)
+
+struct Arrays
+{
+  char a1[1];
+  char a4k[4096];
+  char a4kp1[4097];
+#if INT_MAX < LONG_MAX
+  char amax[INT_MAX];
+#else
+  char amax[32767];
+#endif
+  char ax[];
+};
+
+void test_string_unchecked (const char *s, const struct Arrays *ar)
+{
+  /* Verify there is no warning with strings of unknown length.  */
+  T (-1, "%-s", s);
+  T (-1, "%-s", ar->ax);
+
+  T (-1, "%s%s", s, s);
+  T (-1, "%s%s", "", s);
+  T (-1, "%s%s", s, "1");
+  T (-1, "%s%s", "1", s);
+
+  /* Verify there is no warning with strings of length that cannot
+     exceed 4k (because of the array size).  */
+  T (-1, "%-s", ar->a1);
+  T (-1, "%-s", ar->a4k);
+
+  /* Verify there's no "exceeds minimum required size of 4095" warning
+     with multiple %s directives and a combination of strings of unknown
+     (and potentially unbounded) length and strings whose length is
+     bounded by the size of the arrays they are stored in.  */
+  T (-1, "%s%s", s, ar->a4k);
+  T (-1, "%s%s", ar->a4k, s);
+  T (-1, "%s%s", ar->a4k, ar->a4k);
+  T (-1, "%s%s", ar->a4k, "123");
+  T (-1, "%s%s", "123", ar->a4k);
+  T (-1, "%s%s", ar->ax, ar->a4k);
+  T (-1, "%s%s", ar->a4k, ar->ax);
+
+  /* Verify that an array that fits a string longer than 4095 bytes
+     does trigger a warning.  */
+  T (-1, "%-s", ar->a4kp1);   /* { dg-warning "directive output between 0 and 4096 bytes may exceed minimum required size of 4095" } */
+
+  /* Also verify that a %s directive with width greater than 4095
+     triggers a warning even if the argument is not longer than 4k.  */
+  T (-1, "%*s", 4096, ar->a4k);   /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+
+  /* Verify that precision constrains the putput and suppresses the 4k
+     warning.  */
+  T (-1, "%.*s", 4095, ar->a4kp1);
+
+  T (-1, "%s %s", s, "");
+  T (-1, "%s %s", "", s);
+  T (-1, "%s %s", s, "1");
+  T (-1, "%s %s", "1", s);
+
+  T (-1, "%s%s%s", s, "1", s);
+  T (-1, "%s%s%s", "1", s, "1");
+  T (-1, "%s%s%s", s, s, s);
+  T (-1, "%*s%*s%*s", 4093, s, 4094, s, 4095, s);
+  T (-1, "%s %s %s", s, s, s);
+  T (-1, "%s %s %s", ar->a4k, ar->a4k, ar->a4k);
+  T (-1, "%s %s %s", ar->ax, ar->ax, ar->ax);
+
+  /* Verify that an array of INT_MAX elements doesn't trigger the INT_MAX
+     warning (LP64 only).  */
+  T (-1, "%-s", ar->amax);   /* { dg-warning "directive output between 0 and \[0-9\]+ bytes may exceed minimum required size of 4095" } */
+}
+
+#undef T
+/* Helper test macro.  */
+#define T(size, ...)					\
+  do {							\
+    size_t n = size < 0 ? unknown_size : size;		\
+    char *buf = size < 0 ? unknown_buffer		\
+      : n < sizeof fixed_buffer				\
+      ? fixed_buffer + sizeof fixed_buffer - size	\
+      : unknown_buffer;					\
+    int r = FUNC (snprintf) (buf, n, __VA_ARGS__);	\
+    sink (r, fixed_buffer, unknown_buffer);		\
+  } while (0)
+
+void test_string_checked (const char *s, const struct Arrays *ar)
+{
+  /* Verify there is no warning with strings of unknown length.  */
+  T (-1, "%-s", s);
+  T (-1, "%-s", ar->ax);
+
+  T (-1, "%s%s", s, s);
+  T (-1, "%s%s", "", s);
+  T (-1, "%s%s", s, "1");
+  T (-1, "%s%s", "1", s);
+
+  /* Verify there is no warning with strings of length that cannot
+     exceed 4k (because of the array size).  */
+  T (-1, "%-s", ar->a1);
+  T (-1, "%-s", ar->a4k);
+
+  /* Verify there's no "exceeds minimum required size of 4095" warning
+     with multiple %s directives and a combination of strings of unknown
+     (and potentially unbounded) length and strings whose length is
+     bounded by the size of the arrays they are stored in.  */
+  T (-1, "%s%s", s, ar->a4k);
+  T (-1, "%s%s", ar->a4k, s);
+  T (-1, "%s%s", ar->a4k, ar->a4k);
+  T (-1, "%s%s", ar->a4k, "123");
+  T (-1, "%s%s", "123", ar->a4k);
+  T (-1, "%s%s", ar->ax, ar->a4k);
+  T (-1, "%s%s", ar->a4k, ar->ax);
+
+  /* Verify that an array that fits a string longer than 4095 bytes
+     does trigger a warning.  */
+  T (-1, "%-s", ar->a4kp1);   /* { dg-warning "directive output between 0 and 4096 bytes may exceed minimum required size of 4095" } */
+
+  /* Also verify that a %s directive with width greater than 4095
+     triggers a warning even if the argument is not longer than 4k.  */
+  T (-1, "%*s", 4096, ar->a4k);   /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+
+  /* Verify that precision constrains the putput and suppresses the 4k
+     warning.  */
+  T (-1, "%.*s", 4095, ar->a4kp1);
+
+  T (-1, "%s %s", s, "");
+  T (-1, "%s %s", "", s);
+  T (-1, "%s %s", s, "1");
+  T (-1, "%s %s", "1", s);
+
+  T (-1, "%s%s%s", s, "1", s);
+  T (-1, "%s%s%s", "1", s, "1");
+  T (-1, "%s%s%s", s, s, s);
+  T (-1, "%*s%*s%*s", 4093, s, 4094, s, 4095, s);
+  T (-1, "%s %s %s", s, s, s);
+  T (-1, "%s %s %s", ar->a4k, ar->a4k, ar->a4k);
+  T (-1, "%s %s %s", ar->ax, ar->ax, ar->ax);
+
+  T (-1, "%-s", ar->amax);   /* { dg-warning "directive output between 0 and \[0-9\]+ bytes may exceed minimum required size of 4095" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr79448-2.c b/gcc/testsuite/gcc.dg/tree-ssa/pr79448-2.c
new file mode 100644
index 0000000..f75f523
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr79448-2.c
@@ -0,0 +1,21 @@
+/* PR middle-end/79448 - unhelpful -Wformat-truncation=2 warning
+   Verify that there's no warning with optimization.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wformat -Wformat-truncation=2" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern int
+snprintf (char*, size_t, const char*, ...);
+
+char*
+gettext (char*);
+
+char*
+fill (char *buf, size_t len, int count)
+{
+  if (snprintf (buf, len, "%s: %d", gettext ("count"), count) >= len)  /* { dg-bogus "directive output of 2 bytes causes result to exceed .INT_MAX." */
+    return 0;
+
+  return buf;
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr79448.c b/gcc/testsuite/gcc.dg/tree-ssa/pr79448.c
new file mode 100644
index 0000000..c346c9e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr79448.c
@@ -0,0 +1,21 @@
+/* PR middle-end/79448 - unhelpful -Wformat-truncation=2 warning
+   Verify that there's no warning without optimization.
+   { dg-do compile }
+   { dg-options "-Wall -Wformat -Wformat-truncation=2" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern int
+snprintf (char*, size_t, const char*, ...);
+
+char*
+gettext (char*);
+
+char*
+fill (char *buf, size_t len, int count)
+{
+  if (snprintf (buf, len, "%s: %d", gettext ("count"), count) >= len)  /* { dg-bogus "directive output of 2 bytes causes result to exceed .INT_MAX." */
+    return 0;
+
+  return buf;
+}
