PR 87041 - -Wformat "reading through null pointer" on unreachable
code is a complaint about -Wformat false positives due to null
arguments to %s directives in unreachable printf calls.  The warning
is issued by the front end, too early to know whether or not the call
is ever made.

The -Wformat-overflow has had the ability to detect null pointers
in %s and similar directives to sprintf calls since GCC 7 without
these false positives, but the warning doesn't consider stream or
file I/O functions like printf/fprintf.  To resolve the bug report
I have enhanced -Wformat-overflow to consider all printf-like
functions, including user-defined ones declared attribute format
(printf).

Besides null pointers the enhancement also makes it possible to
detect other problems (like out-of-range arguments and output in
excess of INT_MAX bytes).  It also lays the groundwork for
checking user-defined printf-like functions for buffer overflow
(once a suitable attribute is added to indicate which arguments
are the destination buffer pointer and the buffer size).

With that, I have removed the null checking from -Wformat (again,
only for printf-like functions).

Martin
PR middle-end/87041 - -Wformat reading through null pointer on unreachable code

gcc/ChangeLog:

	PR middle-end/87041
	* gimple-ssa-sprintf.c (format_directive): Use %G to include
	inlining context.
	(sprintf_dom_walker::compute_format_length):
	Avoid setting POSUNDER4K here.
	(get_destination_size): Handle null argument values.
	(get_user_idx_format): New function.
	(sprintf_dom_walker::handle_gimple_call): Handle all printf-like
	functions, including user-defined with attribute format printf.
	Use %G to include inlining context.
	Set POSUNDER4K here.

gcc/c-family/ChangeLog:

	PR middle-end/87041
	* c-format.c (check_format_types): Avoid diagnosing null pointer
	arguments to printf-family of functions.

gcc/testsuite/ChangeLog:

	PR middle-end/87041
	* gcc.c-torture/execute/fprintf-2.c: New test.
	* gcc.c-torture/execute/printf-2.c: Same.
	* gcc.c-torture/execute/user-printf.c: Same.
	* gcc.dg/tree-ssa/builtin-fprintf-warn-1.c: Same.
	* gcc.dg/tree-ssa/builtin-printf-2.c: Same.
	* gcc.dg/tree-ssa/builtin-printf-warn-1.c: Same.
	* gcc.dg/tree-ssa/user-printf-warn-1.c: Same.

Index: gcc/c-family/c-format.c
===================================================================
--- gcc/c-family/c-format.c	(revision 265496)
+++ gcc/c-family/c-format.c	(working copy)
@@ -3123,8 +3123,11 @@ check_format_types (const substring_loc &fmt_loc,
 		warning (OPT_Wformat_, "writing through null pointer "
 			 "(argument %d)", arg_num);
 
-	      /* Check for reading through a NULL pointer.  */
-	      if (types->reading_from_flag
+	      /* Check for reading through a NULL pointer.  Ignore
+		 printf-family of functions as they are checked for
+		 null arguments by the middle-end.  */
+	      if (fki->conversion_specs != print_char_table
+		  && types->reading_from_flag
 		  && i == 0
 		  && cur_param != 0
 		  && integer_zerop (cur_param))
Index: gcc/gimple-ssa-sprintf.c
===================================================================
--- gcc/gimple-ssa-sprintf.c	(revision 265496)
+++ gcc/gimple-ssa-sprintf.c	(working copy)
@@ -68,6 +68,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "langhooks.h"
 
+#include "attribs.h"
 #include "builtins.h"
 #include "stor-layout.h"
 
@@ -2796,8 +2797,9 @@ format_directive (const sprintf_dom_walker::call_i
   if (fmtres.nullp)
     {
       fmtwarn (dirloc, argloc, NULL, info.warnopt (),
-	       "%<%.*s%> directive argument is null",
-	       dirlen, target_to_host (hostdir, sizeof hostdir, dir.beg));
+	       "%G%<%.*s%> directive argument is null",
+	       info.callstmt, dirlen,
+	       target_to_host (hostdir, sizeof hostdir, dir.beg));
 
       /* Don't bother processing the rest of the format string.  */
       res->warned = true;
@@ -3475,7 +3477,6 @@ sprintf_dom_walker::compute_format_length (call_in
      by the known range [0, 0] (with no conversion resulting in a failure
      or producing more than 4K bytes) until determined otherwise.  */
   res->knownrange = true;
-  res->posunder4k = true;
   res->floating = false;
   res->warned = false;
 
@@ -3518,6 +3519,10 @@ sprintf_dom_walker::compute_format_length (call_in
 static unsigned HOST_WIDE_INT
 get_destination_size (tree dest)
 {
+  /* When there is no destination return -1.  */
+  if (!dest)
+    return HOST_WIDE_INT_M1U;
+
   /* Initialize object size info before trying to compute it.  */
   init_object_sizes ();
 
@@ -3738,6 +3743,37 @@ try_simplify_call (gimple_stmt_iterator *gsi,
   return false;
 }
 
+/* Return the zero-based index of the format string argument of a printf
+   like function and set *IDX_ARGS to the first format argument.  When
+   no such index exists return UINT_MAX.  */
+
+static unsigned
+get_user_idx_format (tree fndecl, unsigned *idx_args)
+{
+  tree attrs = lookup_attribute ("format", DECL_ATTRIBUTES (fndecl));
+  if (!attrs)
+    attrs = lookup_attribute ("format", TYPE_ATTRIBUTES (TREE_TYPE (fndecl)));
+
+  if (!attrs)
+    return UINT_MAX;
+
+  attrs = TREE_VALUE (attrs);
+
+  tree archetype = TREE_VALUE (attrs);
+  if (strcmp ("printf", IDENTIFIER_POINTER (archetype)))
+    return UINT_MAX;
+
+  attrs = TREE_CHAIN (attrs);
+  tree fmtarg = TREE_VALUE (attrs);
+
+  attrs = TREE_CHAIN (attrs);
+  tree elliparg = TREE_VALUE (attrs);
+
+  /* Attribute argument indices are 1-based but we use zero-based.  */
+  *idx_args = tree_to_uhwi (elliparg) - 1;
+  return tree_to_uhwi (fmtarg) - 1;
+}
+
 /* Determine if a GIMPLE CALL is to one of the sprintf-like built-in
    functions and if so, handle it.  Return true if the call is removed
    and gsi_next should not be performed in the caller.  */
@@ -3748,12 +3784,23 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
   call_info info = call_info ();
 
   info.callstmt = gsi_stmt (*gsi);
-  if (!gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
+  info.func = gimple_call_fndecl (info.callstmt);
+  if (!info.func)
     return false;
 
-  info.func = gimple_call_fndecl (info.callstmt);
   info.fncode = DECL_FUNCTION_CODE (info.func);
 
+  /* Format string argument number (valid for all functions).  */
+  unsigned idx_format = UINT_MAX;
+  if (!gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
+    {
+      unsigned idx_args;
+      idx_format = get_user_idx_format (info.func, &idx_args);
+      if (idx_format == UINT_MAX)
+	return false;
+      info.argidx = idx_args;
+    }
+
   /* The size of the destination as in snprintf(dest, size, ...).  */
   unsigned HOST_WIDE_INT dstsize = HOST_WIDE_INT_M1U;
 
@@ -3760,17 +3807,70 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
   /* The size of the destination determined by __builtin_object_size.  */
   unsigned HOST_WIDE_INT objsize = HOST_WIDE_INT_M1U;
 
-  /* Buffer size argument number (snprintf and vsnprintf).  */
-  unsigned HOST_WIDE_INT idx_dstsize = HOST_WIDE_INT_M1U;
+  /* Zero-based buffer size argument number (snprintf and vsnprintf).  */
+  unsigned idx_dstsize = UINT_MAX;
 
   /* Object size argument number (snprintf_chk and vsnprintf_chk).  */
-  unsigned HOST_WIDE_INT idx_objsize = HOST_WIDE_INT_M1U;
+  unsigned idx_objsize = UINT_MAX;
 
-  /* Format string argument number (valid for all functions).  */
-  unsigned idx_format;
+  /* Destinaton argument number (valid for sprintf functions only).  */
+  unsigned idx_dstptr = 0;
 
   switch (info.fncode)
     {
+    case BUILT_IN_NONE:
+      // User-defined function with attribute format (printf).
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_FPRINTF:
+      // Signature:
+      //   __builtin_fprintf (FILE*, format, ...)
+      idx_format = 1;
+      info.argidx = 2;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_FPRINTF_CHK:
+      // Signature:
+      //   __builtin_fprintf_chk (FILE*, ost, format, ...)
+      idx_format = 2;
+      info.argidx = 3;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_FPRINTF_UNLOCKED:
+      // Signature:
+      //   __builtin_fprintf_unnlocked (FILE*, format, ...)
+      idx_format = 1;
+      info.argidx = 2;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_PRINTF:
+      // Signature:
+      //   __builtin_printf (format, ...)
+      idx_format = 0;
+      info.argidx = 1;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_PRINTF_CHK:
+      // Signature:
+      //   __builtin_printf_chk (it, format, ...)
+      idx_format = 1;
+      info.argidx = 2;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_PRINTF_UNLOCKED:
+      // Signature:
+      //   __builtin_printf (format, ...)
+      idx_format = 0;
+      info.argidx = 1;
+      idx_dstptr = -1;
+      break;
+
     case BUILT_IN_SPRINTF:
       // Signature:
       //   __builtin_sprintf (dst, format, ...)
@@ -3805,6 +3905,38 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
       info.bounded = true;
       break;
 
+    case BUILT_IN_VFPRINTF:
+      // Signature:
+      //   __builtin_vprintf (FILE*, format, va_list)
+      idx_format = 1;
+      info.argidx = -1;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_VFPRINTF_CHK:
+      // Signature:
+      //   __builtin___vfprintf_chk (FILE*, ost, format, va_list)
+      idx_format = 2;
+      info.argidx = -1;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_VPRINTF:
+      // Signature:
+      //   __builtin_vprintf (format, va_list)
+      idx_format = 0;
+      info.argidx = -1;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_VPRINTF_CHK:
+      // Signature:
+      //   __builtin___vprintf_chk (ost, format, va_list)
+      idx_format = 1;
+      info.argidx = -1;
+      idx_dstptr = -1;
+      break;
+
     case BUILT_IN_VSNPRINTF:
       // Signature:
       //   __builtin_vsprintf (dst, size, format, va)
@@ -3846,8 +3978,10 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
   /* Set the global warning level for this function.  */
   warn_level = info.bounded ? warn_format_trunc : warn_format_overflow;
 
-  /* The first argument is a pointer to the destination.  */
-  tree dstptr = gimple_call_arg (info.callstmt, 0);
+  /* For all string functions the first argument is a pointer to
+     the destination.  */
+  tree dstptr = (idx_dstptr < gimple_call_num_args (info.callstmt)
+		 ? gimple_call_arg (info.callstmt, 0) : NULL_TREE);
 
   info.format = gimple_call_arg (info.callstmt, idx_format);
 
@@ -3855,7 +3989,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
      or upper bound of a range.  */
   bool dstsize_cst_p = true;
 
-  if (idx_dstsize == HOST_WIDE_INT_M1U)
+  if (idx_dstsize == UINT_MAX)
     {
       /* For non-bounded functions like sprintf, determine the size
 	 of the destination from the object or pointer passed to it
@@ -3880,7 +4014,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
 	      /* Avoid warning if -Wstringop-overflow is specified since
 		 it also warns for the same thing though only for the
 		 checking built-ins.  */
-	      if ((idx_objsize == HOST_WIDE_INT_M1U
+	      if ((idx_objsize == UINT_MAX
 		   || !warn_stringop_overflow))
 		warning_at (gimple_location (info.callstmt), info.warnopt (),
 			    "specified bound %wu exceeds maximum object size "
@@ -3910,7 +4044,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
 	}
     }
 
-  if (idx_objsize != HOST_WIDE_INT_M1U)
+  if (idx_objsize != UINT_MAX)
     if (tree size = gimple_call_arg (info.callstmt, idx_objsize))
       if (tree_fits_uhwi_p (size))
 	objsize = tree_to_uhwi (size);
@@ -3930,7 +4064,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
       /* For calls to non-bounded functions or to those of bounded
 	 functions with a non-zero size, warn if the destination
 	 pointer is null.  */
-      if (integer_zerop (dstptr))
+      if (dstptr && integer_zerop (dstptr))
 	{
 	  /* This is diagnosed with -Wformat only when the null is a constant
 	     pointer.  The warning here diagnoses instances where the pointer
@@ -3937,7 +4071,8 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
 	     is not constant.  */
 	  location_t loc = gimple_location (info.callstmt);
 	  warning_at (EXPR_LOC_OR_LOC (dstptr, loc),
-		      info.warnopt (), "null destination pointer");
+		      info.warnopt (), "%Gnull destination pointer",
+		      info.callstmt);
 	  return false;
 	}
 
@@ -3950,7 +4085,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
 	  /* Avoid warning if -Wstringop-overflow is specified since
 	     it also warns for the same thing though only for the
 	     checking built-ins.  */
-	  && (idx_objsize == HOST_WIDE_INT_M1U
+	  && (idx_objsize == UINT_MAX
 	      || !warn_stringop_overflow))
 	{
 	  warning_at (gimple_location (info.callstmt), info.warnopt (),
@@ -3959,14 +4094,15 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
 	}
     }
 
-  if (integer_zerop (info.format))
+  /* Determine if the format argument may be null and warn if not
+     and if the argument is null.  */
+  if (integer_zerop (info.format)
+      && gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
     {
-      /* This is diagnosed with -Wformat only when the null is a constant
-	 pointer.  The warning here diagnoses instances where the pointer
-	 is not constant.  */
       location_t loc = gimple_location (info.callstmt);
       warning_at (EXPR_LOC_OR_LOC (info.format, loc),
-		  info.warnopt (), "null format string");
+		  info.warnopt (), "%Gnull format string",
+		  info.callstmt);
       return false;
     }
 
@@ -3978,6 +4114,14 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
      including the terminating NUL.  */
   format_result res = format_result ();
 
+  /* I/O functions with no destination argument (i.e., all forms of fprintf
+     and printf) may fail under any conditions.  Others (i.e., all forms of
+     sprintf) may only fail under specific conditions determined for each
+     directive.  Clear POSUNDER4K for the former set of functions and set
+     it to true for the latter (it can only be cleared later, but it is
+     never set to true again). */
+  res.posunder4k = dstptr;
+
   bool success = compute_format_length (info, &res);
   if (res.warned)
     gimple_set_no_warning (info.callstmt, true);
Index: gcc/testsuite/gcc.c-torture/execute/fprintf-2.c
===================================================================
--- gcc/testsuite/gcc.c-torture/execute/fprintf-2.c	(nonexistent)
+++ gcc/testsuite/gcc.c-torture/execute/fprintf-2.c	(working copy)
@@ -0,0 +1,53 @@
+/* Verify that calls to fprintf don't get eliminated even if their
+   result on success can be computed at compile time (they can fail).
+   The calls can still be transformed into those of other functions.
+   { dg-skip-if "requires io" { freestanding } } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main (void)
+{
+  char *tmpfname = tmpnam (0);
+  FILE *f = fopen (tmpfname, "w");
+  if (!f)
+    {
+      perror ("fopen for writing");
+      return 1;
+    }
+
+  fprintf (f, "1");
+  fprintf (f, "%c", '2');
+  fprintf (f, "%c%c", '3', '4');
+  fprintf (f, "%s", "5");
+  fprintf (f, "%s%s", "6", "7");
+  fprintf (f, "%i", 8);
+  fprintf (f, "%.1s\n", "9x");
+  fclose (f);
+
+  f = fopen (tmpfname, "r");
+  if (!f)
+    {
+      perror ("fopen for reading");
+      remove (tmpfname);
+      return 1;
+    }
+
+  char buf[12] = "";
+  if (1 != fscanf (f, "%s", buf))
+    {
+      perror ("fscanf");
+      fclose (f);
+      remove (tmpfname);
+      return 1;
+    }
+
+  fclose (f);
+  remove (tmpfname);
+
+  if (strcmp (buf, "123456789"))
+    abort ();
+
+  return 0;
+}
Index: gcc/testsuite/gcc.c-torture/execute/printf-2.c
===================================================================
--- gcc/testsuite/gcc.c-torture/execute/printf-2.c	(nonexistent)
+++ gcc/testsuite/gcc.c-torture/execute/printf-2.c	(working copy)
@@ -0,0 +1,60 @@
+/* Verify that calls to printf don't get eliminated even if their
+   result on success can be computed at compile time (they can fail).
+   The calls can still be transformed into those of other functions.
+   { dg-skip-if "requires io" { freestanding } } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+__attribute__ ((noipa)) void
+write_file (void)
+{
+  printf ("1");
+  printf ("%c", '2');
+  printf ("%c%c", '3', '4');
+  printf ("%s", "5");
+  printf ("%s%s", "6", "7");
+  printf ("%i", 8);
+  printf ("%.1s\n", "9x");
+}
+
+
+int main (void)
+{
+  char *tmpfname = tmpnam (0);
+  FILE *f = freopen (tmpfname, "w", stdout);
+  if (!f)
+    {
+      perror ("fopen for writing");
+      return 1;
+    }
+
+  write_file ();
+  fclose (f);
+
+  f = fopen (tmpfname, "r");
+  if (!f)
+    {
+      perror ("fopen for reading");
+      remove (tmpfname);
+      return 1;
+    }
+
+  char buf[12] = "";
+  if (1 != fscanf (f, "%s", buf))
+    {
+      perror ("fscanf");
+      fclose (f);
+      remove (tmpfname);
+      return 1;
+    }
+
+  fclose (f);
+  remove (tmpfname);
+
+  if (strcmp (buf, "123456789"))
+    abort ();
+
+  return 0;
+}
Index: gcc/testsuite/gcc.c-torture/execute/user-printf.c
===================================================================
--- gcc/testsuite/gcc.c-torture/execute/user-printf.c	(nonexistent)
+++ gcc/testsuite/gcc.c-torture/execute/user-printf.c	(working copy)
@@ -0,0 +1,64 @@
+/* Verify that calls to a function declared wiith attribute format (printf)
+   don't get eliminated even if their result on success can be computed at
+   compile time (they can fail).
+   { dg-skip-if "requires io" { freestanding } } */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void __attribute__ ((format (printf, 1, 2), noipa))
+user_print (const char *fmt, ...)
+{
+  va_list va;
+  va_start (va, fmt);
+  vfprintf (stdout, fmt, va);
+  va_end (va);
+}
+
+int main (void)
+{
+  char *tmpfname = tmpnam (0);
+  FILE *f = freopen (tmpfname, "w", stdout);
+  if (!f)
+    {
+      perror ("fopen for writing");
+      return 1;
+    }
+
+  user_print ("1");
+  user_print ("%c", '2');
+  user_print ("%c%c", '3', '4');
+  user_print ("%s", "5");
+  user_print ("%s%s", "6", "7");
+  user_print ("%i", 8);
+  user_print ("%.1s\n", "9x");
+
+  fclose (f);
+
+  f = fopen (tmpfname, "r");
+  if (!f)
+    {
+      perror ("fopen for reading");
+      remove (tmpfname);
+      return 1;
+    }
+
+  char buf[12] = "";
+  if (1 != fscanf (f, "%s", buf))
+    {
+      perror ("fscanf");
+      fclose (f);
+      remove (tmpfname);
+      return 1;
+    }
+
+  fclose (f);
+  remove (tmpfname);
+
+  if (strcmp (buf, "123456789"))
+    abort ();
+
+  return 0;
+}
Index: gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c
===================================================================
--- gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c	(working copy)
@@ -0,0 +1,132 @@
+/* PR middle-end/87041 - -Wformat "reading through null pointer" on
+   unreachable code
+   Test to verify that the applicable subset of -Wformat-overflow warnings
+   are issued for the fprintf function.
+   { dg-do compile }
+   { dg-options "-O -Wformat -Wformat-overflow=1 -ftrack-macro-expansion=0" }
+   { dg-require-effective-target int32plus } */
+
+/* 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
+
+#define INT_MAX __INT_MAX__
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef __WINT_TYPE__ wint_t;
+
+void sink (void*, ...);
+
+/* Declare as void* to work around bug 87775.  */
+typedef void FILE;
+
+int dummy_fprintf (FILE*, const char*, ...);
+
+FILE *fp;
+
+const char chr_no_nul = 'a';
+const char arr_no_nul[] = { 'a', 'b' };
+
+
+/* Helper to expand function to either __builtin_f or dummy_f to
+   make debugging GCC easy.  */
+#define T(...)							\
+  (((!LINE || LINE == __LINE__)					\
+    ? __builtin_fprintf : dummy_fprintf) (fp, __VA_ARGS__))
+
+/* Exercise the "%c" directive with constant arguments.  */
+
+void test_fprintf_c_const (int width)
+{
+  /* Verify that a warning is issued for exceeding INT_MAX bytes and
+     not otherwise.  */
+  T ("%*c",  INT_MAX - 1, '1');
+  T ("%*c",  INT_MAX,     '1');
+  T ("X%*c", INT_MAX - 1, '1');
+  T ("X%*c", INT_MAX,     '1');   /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T ("%*c%*c", INT_MAX - 1, '1', INT_MAX - 1, '2'); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T ("%*cX", INT_MAX - 2, '1');
+  T ("%*cX", INT_MAX - 1, '1');
+  T ("%*cX", INT_MAX,     '1');   /* { dg-warning "output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  if (width < INT_MAX - 1)
+    width = INT_MAX - 1;
+  T ("%*cX", width, '1');
+  T ("%*cXY", width, '1');        /* { dg-warning ".XY. directive output of 2 bytes causes result to exceed .INT_MAX." } */
+
+  /* Also exercise a non-constant format string.  The warning points
+     to the line where the format is declared (see bug 87773) so avoid
+     triggering that bug here.  */
+  const char *fmt = "%*cXYZ";  T (fmt, width, '1');            /* { dg-warning ".XYZ. directive output of 3 bytes causes result to exceed .INT_MAX." } */
+}
+
+
+/* Exercise the "%s" directive with constant arguments.  */
+
+void test_fprintf_s_const (int width)
+{
+  const char *nulptr = 0;
+
+  T ("%s", nulptr);               /* { dg-warning "\\\[-Wformat|-Wnonnull" } */
+  T ("%.0s", nulptr);             /* { dg-warning ".%.0s. directive argument is null" } */
+
+  /* Verify no warning is issued for unreachable code.  */
+  if (nulptr)
+    T ("%s", nulptr);
+
+  T ("%s", &chr_no_nul);          /* { dg-warning ".%s. directive argument is not a nul-terminated string" } */
+  T ("%s", arr_no_nul);           /* { dg-warning ".%s. directive argument is not a nul-terminated string" } */
+
+  /* Verify that output in excess of INT_MAX bytes is diagnosed even
+     when the size of the destination object is unknown.  */
+  T ("%*s",  INT_MAX - 1, "");
+  T ("%*s",  INT_MAX,     "");
+  T ("X%*s", INT_MAX,     "");    /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  if (width < INT_MAX - 1)
+    width = INT_MAX - 1;
+  T ("%*sX", width, "1");
+  T ("%*sXY", width, "1");        /* { dg-warning ".XY. directive output of 2 bytes causes result to exceed .INT_MAX." } */
+}
+
+
+const wchar_t wchr_no_nul = L'a';
+const wchar_t warr_no_nul[] = { L'a', L'b' };
+
+/* Exercise the "%s" directive with constant arguments.  */
+
+void test_fprintf_ls_const (int width)
+{
+  const wchar_t *nulptr = 0;
+
+  T ("%ls", nulptr);              /* { dg-warning ".%ls. directive argument is null" } */
+  T ("%.0ls", nulptr);            /* { dg-warning ".%.0ls. directive argument is null" } */
+
+  /* Verify no warning is issued for unreachable code.  */
+  if (nulptr)
+    T ("%ls", nulptr);
+
+  T ("%ls", &wchr_no_nul);        /* { dg-warning ".%ls. directive argument is not a nul-terminated string" } */
+  T ("%ls", warr_no_nul);         /* { dg-warning ".%ls. directive argument is not a nul-terminated string" } */
+
+  /* Verify that output in excess of INT_MAX bytes is diagnosed even
+     when the size of the destination object is unknown.  */
+  T ("%*ls",  INT_MAX - 1, L"");
+  T ("%*ls",  INT_MAX,     L"");
+  T ("X%*ls", INT_MAX,     L"");  /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  if (width < INT_MAX - 1)
+    width = INT_MAX - 1;
+  T ("%*lsX", width, L"1");
+  T ("%*lsXY", width, L"1");      /* { dg-warning ".XY. directive output of 2 bytes causes result to exceed .INT_MAX." } */
+}
Index: gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-2.c
===================================================================
--- gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-2.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-2.c	(working copy)
@@ -0,0 +1,213 @@
+/* Verify that tests for the result of calls to fprintf, printf, vfprintf,
+   and vprintf are not eliminated, even if it is possible to determine
+   their value on success (the calls may fail and return a negative value).
+   { dg-do compile }
+   { dg-options "-O2 -fdump-tree-optimized" } */
+
+typedef struct FILE FILE;
+typedef __builtin_va_list va_list;
+
+extern int printf (const char *, ...);
+extern int printf_unlocked (const char *, ...);
+extern int vprintf (const char *, va_list);
+
+extern int fprintf (FILE*, const char *, ...);
+extern int fprintf_unlocked (FILE*, const char *, ...);
+extern int vfprintf (FILE*, const char *, va_list);
+
+#define fprintf_chk    __builtin___fprintf_chk
+#define printf_chk     __builtin___printf_chk
+#define vfprintf_chk   __builtin___vfprintf_chk
+#define vprintf_chk    __builtin___vprintf_chk
+
+#define CAT(s, n)   s ## n
+
+#define KEEP(func, line)   CAT (func ## _test_on_line_, line)
+
+/* Emit one call to a function named call_on_line_NNN when the result
+   of the call FUNC ARGS is less than zero, zero, or greater than zero.
+   This verifies that the expression is not eliminated.
+
+   For known output it is possible to bound the return value to
+   [INT_MIN, -1] U [0, N] with N being the size of the output, but
+   that optimization isn't implemented (yet).  */
+
+#define T(func, args)						\
+  do {								\
+    extern void KEEP (func, __LINE__)(const char*);		\
+    if ((func args) < 0) KEEP (func, __LINE__)("< 0");		\
+    if ((func args) >= 0) KEEP (func, __LINE__)(">= 0");	\
+  } while (0)
+
+void test_fprintf (FILE *f, const char *s)
+{
+  /* Here the result is in [INT_MIN, 0], i.e., it cannot be positive.
+     It might be a useful enhancement to implement this optimization.  */
+  T (fprintf, (f, ""));
+  T (fprintf, (f, "1"));
+  T (fprintf, (f, "123"));
+  T (fprintf, (f, s));
+
+  T (fprintf, (f, "%c", 0));
+  T (fprintf, (f, "%c", '1'));
+  T (fprintf, (f, "%c", *s));
+
+  T (fprintf, (f, "%s", ""));
+  T (fprintf, (f, "%s", "1"));
+  T (fprintf, (f, "%.0s", ""));
+  T (fprintf, (f, "%.0s", s));
+
+  /* { dg-final { scan-tree-dump-times " fprintf_test_on_line_" 22 "optimized"} } */
+}
+
+
+void test_fprintf_unlocked (FILE *f, const char *s)
+{
+  T (fprintf_unlocked, (f, ""));
+  T (fprintf_unlocked, (f, "1"));
+  T (fprintf_unlocked, (f, "123"));
+  T (fprintf_unlocked, (f, s));
+
+  T (fprintf_unlocked, (f, "%c", 0));
+  T (fprintf_unlocked, (f, "%c", '1'));
+  T (fprintf_unlocked, (f, "%c", *s));
+
+  T (fprintf_unlocked, (f, "%s", ""));
+  T (fprintf_unlocked, (f, "%s", "1"));
+  T (fprintf_unlocked, (f, "%.0s", ""));
+  T (fprintf_unlocked, (f, "%.0s", s));
+
+  /* { dg-final { scan-tree-dump-times " fprintf_unlocked_test_on_line_" 22 "optimized"} } */
+}
+
+
+void test_fprintf_chk (FILE *f, const char *s)
+{
+  T (fprintf_chk, (f, 0, ""));
+  T (fprintf_chk, (f, 0, "1"));
+  T (fprintf_chk, (f, 0, "123"));
+  T (fprintf_chk, (f, 0, s));
+
+  T (fprintf_chk, (f, 0, "%c", 0));
+  T (fprintf_chk, (f, 0, "%c", '1'));
+  T (fprintf_chk, (f, 0, "%c", *s));
+
+  T (fprintf_chk, (f, 0, "%s", ""));
+  T (fprintf_chk, (f, 0, "%s", "1"));
+  T (fprintf_chk, (f, 0, "%.0s", ""));
+  T (fprintf_chk, (f, 0, "%.0s", s));
+
+  /* { dg-final { scan-tree-dump-times " __builtin___fprintf_chk_test_on_line_" 22 "optimized"} } */
+}
+
+
+void test_vfprintf (FILE *f, va_list va)
+{
+  T (vfprintf, (f, "", va));
+  T (vfprintf, (f, "123", va));
+
+  T (vfprintf, (f, "%c", va));
+
+  T (vfprintf, (f, "%.0s", va));
+
+  /* { dg-final { scan-tree-dump-times " vfprintf_test_on_line_" 8 "optimized"} } */
+}
+
+
+void test_vfprintf_chk (FILE *f, va_list va)
+{
+  T (vfprintf_chk, (f, 0, "", va));
+  T (vfprintf_chk, (f, 0, "123", va));
+
+  T (vfprintf_chk, (f, 0, "%c", va));
+
+  T (vfprintf_chk, (f, 0, "%.0s", va));
+
+  /* { dg-final { scan-tree-dump-times " __builtin___vfprintf_chk_test_on_line_" 8 "optimized"} } */
+}
+
+
+void test_printf (const char *s)
+{
+  T (printf, (""));
+  T (printf, ("1"));
+  T (printf, ("123"));
+  T (printf, (s));
+
+  T (printf, ("%c", 0));
+  T (printf, ("%c", '1'));
+  T (printf, ("%c", *s));
+
+  T (printf, ("%s", ""));
+  T (printf, ("%s", "1"));
+  T (printf, ("%.0s", ""));
+  T (printf, ("%.0s", s));
+
+/* { dg-final { scan-tree-dump-times " printf_test_on_line_" 22 "optimized"} } */
+}
+
+
+void test_printf_unlocked (const char *s)
+{
+  T (printf_unlocked, (""));
+  T (printf_unlocked, ("1"));
+  T (printf_unlocked, ("123"));
+  T (printf_unlocked, (s));
+
+  T (printf_unlocked, ("%c", 0));
+  T (printf_unlocked, ("%c", '1'));
+  T (printf_unlocked, ("%c", *s));
+
+  T (printf_unlocked, ("%s", ""));
+  T (printf_unlocked, ("%s", "1"));
+  T (printf_unlocked, ("%.0s", ""));
+  T (printf_unlocked, ("%.0s", s));
+
+/* { dg-final { scan-tree-dump-times " printf_unlocked_test_on_line_" 22 "optimized"} } */
+}
+
+
+void test_printf_chk (const char *s)
+{
+  T (printf_chk, (0, ""));
+  T (printf_chk, (0, "1"));
+  T (printf_chk, (0, "123"));
+  T (printf_chk, (0, s));
+
+  T (printf_chk, (0, "%c", 0));
+  T (printf_chk, (0, "%c", '1'));
+  T (printf_chk, (0, "%c", *s));
+
+  T (printf_chk, (0, "%s", ""));
+  T (printf_chk, (0, "%s", "1"));
+  T (printf_chk, (0, "%.0s", ""));
+  T (printf_chk, (0, "%.0s", s));
+
+/* { dg-final { scan-tree-dump-times " __builtin___printf_chk_test_on_line_" 22 "optimized"} } */
+}
+
+
+void test_vprintf (va_list va)
+{
+  T (vprintf, ("", va));
+  T (vprintf, ("123", va));
+
+  T (vprintf, ("%c", va));
+
+  T (vprintf, ("%.0s", va));
+
+  /* { dg-final { scan-tree-dump-times " vprintf_test_on_line_" 8 "optimized"} } */
+}
+
+
+void test_vprintf_chk (va_list va)
+{
+  T (vprintf_chk, (0, "", va));
+  T (vprintf_chk, (0, "123", va));
+
+  T (vprintf_chk, (0, "%c", va));
+
+  T (vprintf_chk, (0, "%.0s", va));
+
+  /* { dg-final { scan-tree-dump-times " __builtin___vprintf_chk_test_on_line_" 8 "optimized"} } */
+}
Index: gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-warn-1.c
===================================================================
--- gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-warn-1.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-warn-1.c	(working copy)
@@ -0,0 +1,129 @@
+/* PR middle-end/87041 - -Wformat "reading through null pointer" on
+   unreachable code
+   Test to verify that the applicable subset of -Wformat-overflow warnings
+   are issued for the printf function.
+   { dg-do compile }
+   { dg-options "-O -Wformat -Wformat-overflow=1 -ftrack-macro-expansion=0" }
+   { dg-require-effective-target int32plus } */
+
+/* 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
+
+#define INT_MAX __INT_MAX__
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef __WINT_TYPE__ wint_t;
+
+typedef unsigned char UChar;
+
+void sink (void*, ...);
+
+int dummy_printf (const char*, ...);
+
+const char chr_no_nul = 'a';
+const char arr_no_nul[] = { 'a', 'b' };
+
+
+/* Helper to expand function to either __builtin_f or dummy_f to
+   make debugging GCC easy.  */
+#define T(...)							\
+  (((!LINE || LINE == __LINE__)					\
+    ? __builtin_printf : dummy_printf) (__VA_ARGS__))
+
+/* Exercise the "%c" directive with constant arguments.  */
+
+void test_printf_c_const (int width)
+{
+  /* Verify that a warning is issued for exceeding INT_MAX bytes and
+     not otherwise.  */
+  T ("%*c",  INT_MAX - 1, '1');
+  T ("%*c",  INT_MAX,     '1');
+  T ("X%*c", INT_MAX - 1, '1');
+  T ("X%*c", INT_MAX,     '1');   /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T ("%*c%*c", INT_MAX - 1, '1', INT_MAX - 1, '2'); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T ("%*cX", INT_MAX - 2, '1');
+  T ("%*cX", INT_MAX - 1, '1');
+  T ("%*cX", INT_MAX,     '1');   /* { dg-warning "output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  if (width < INT_MAX - 1)
+    width = INT_MAX - 1;
+  T ("%*cX", width, '1');
+  T ("%*cXY", width, '1');        /* { dg-warning ".XY. directive output of 2 bytes causes result to exceed .INT_MAX." } */
+
+  /* Also exercise a non-constant format string.  The warning points
+     to the line where the format is declared (see bug 87773) so avoid
+     triggering that bug here.  */
+  const char *fmt = "%*cXYZ";  T (fmt, width, '1');            /* { dg-warning ".XYZ. directive output of 3 bytes causes result to exceed .INT_MAX." } */
+}
+
+
+/* Exercise the "%s" directive with constant arguments.  */
+
+void test_printf_s_const (int width)
+{
+  const char *nulptr = 0;
+
+  T ("%s", nulptr);               /* { dg-warning "\\\[-Wformat|-Wnonnull]" } */
+  T ("%.0s", nulptr);             /* { dg-warning ".%.0s. directive argument is null" } */
+
+  /* Verify no warning is issued for unreachable code.  */
+  if (nulptr)
+    T ("%s", nulptr);
+
+  T ("%s", &chr_no_nul);          /* { dg-warning ".%s. directive argument is not a nul-terminated string" } */
+  T ("%s", arr_no_nul);           /* { dg-warning ".%s. directive argument is not a nul-terminated string" } */
+
+  /* Verify that output in excess of INT_MAX bytes is diagnosed even
+     when the size of the destination object is unknown.  */
+  T ("%*s",  INT_MAX - 1, "");
+  T ("%*s",  INT_MAX,     "");
+  T ("X%*s", INT_MAX,     "");    /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  if (width < INT_MAX - 1)
+    width = INT_MAX - 1;
+  T ("%*sX", width, "1");
+  T ("%*sXY", width, "1");        /* { dg-warning ".XY. directive output of 2 bytes causes result to exceed .INT_MAX." } */
+}
+
+
+const wchar_t wchr_no_nul = L'a';
+const wchar_t warr_no_nul[] = { L'a', L'b' };
+
+/* Exercise the "%s" directive with constant arguments.  */
+
+void test_printf_ls_const (int width)
+{
+  const wchar_t *nulptr = 0;
+
+  T ("%ls", nulptr);              /* { dg-warning ".%ls. directive argument is null" } */
+  T ("%.0ls", nulptr);            /* { dg-warning ".%.0ls. directive argument is null" } */
+
+  /* Verify no warning is issued for unreachable code.  */
+  if (nulptr)
+    T ("%ls", nulptr);
+
+  T ("%ls", &wchr_no_nul);        /* { dg-warning ".%ls. directive argument is not a nul-terminated string" } */
+  T ("%ls", warr_no_nul);         /* { dg-warning ".%ls. directive argument is not a nul-terminated string" } */
+
+  /* Verify that output in excess of INT_MAX bytes is diagnosed even
+     when the size of the destination object is unknown.  */
+  T ("%*ls",  INT_MAX - 1, L"");
+  T ("%*ls",  INT_MAX,     L"");
+  T ("X%*ls", INT_MAX,     L"");  /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  if (width < INT_MAX - 1)
+    width = INT_MAX - 1;
+  T ("%*lsX", width, L"1");
+  T ("%*lsXY", width, L"1");      /* { dg-warning ".XY. directive output of 2 bytes causes result to exceed .INT_MAX." } */
+}
Index: gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c
===================================================================
--- gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c	(nonexistent)
+++ gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c	(working copy)
@@ -0,0 +1,155 @@
+/* PR middle-end/87041 - -Wformat "reading through null pointer" on
+   unreachable code
+   Test to verify that the applicable subset of -Wformat-overflow warnings
+   are issued for the printf function.
+   { dg-do compile }
+   { dg-options "-O -Wformat -Wformat-overflow=1 -ftrack-macro-expansion=0" }
+   { dg-require-effective-target int32plus } */
+
+/* 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
+
+#define INT_MAX __INT_MAX__
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef __WINT_TYPE__ wint_t;
+
+ATTR (format (printf, 2, 3)) void
+user_print (char*, const char*, ...);
+
+ATTR (format (printf, 2, 3), nonnull) void
+user_print_nonnull (char*, const char*, ...);
+
+ATTR (format (printf, 2, 3), nonnull (2)) void
+user_print_nonnull_fmt (char*, const char*, ...);
+
+ATTR (format (printf, 2, 4), nonnull (3)) void
+user_print_nonnull_other (char*, const char*, char*, ...);
+
+void dummy_print (char*, const char*, ...);
+
+const char chr_no_nul = 'a';
+const char arr_no_nul[] = { 'a', 'b' };
+
+
+/* Helper to expand function to either __builtin_f or dummy_f to
+   make debugging GCC easy.  */
+#define T(...)							\
+  (((!LINE || LINE == __LINE__)					\
+    ? user_print : dummy_print) (0, __VA_ARGS__))
+
+/* Exercise the "%c" directive with constant arguments.  */
+
+void test_user_print_format_string (void)
+{
+  char *null = 0;
+  /* Verify that no warning is issued for a null format string unless
+     the corresponding parameter is declared nonnull.  */
+  user_print (0, null);
+  user_print_nonnull ("x", "y");
+  user_print_nonnull ("x", null);   /* { dg-warning "\\\[-Wnonnull]" } */
+  user_print_nonnull_fmt (null, "x");
+  user_print_nonnull_fmt (0, null); /* { dg-warning "\\\[-Wnonnull]" } */
+  user_print_nonnull_other (null, "x", "y");
+  user_print_nonnull_other (null, "x", null);  /* { dg-warning "\\\[-Wnonnull]" } */
+}
+
+
+/* Exercise the "%c" directive with constant arguments.  */
+
+void test_user_print_c_const (int width)
+{
+  /* Verify that a warning is issued for exceeding INT_MAX bytes and
+     not otherwise.  */
+  T ("%*c",  INT_MAX - 1, '1');
+  T ("%*c",  INT_MAX,     '1');
+  T ("X%*c", INT_MAX - 1, '1');
+  T ("X%*c", INT_MAX,     '1');   /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T ("%*c%*c", INT_MAX - 1, '1', INT_MAX - 1, '2'); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T ("%*cX", INT_MAX - 2, '1');
+  T ("%*cX", INT_MAX - 1, '1');
+  T ("%*cX", INT_MAX,     '1');   /* { dg-warning "output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  if (width < INT_MAX - 1)
+    width = INT_MAX - 1;
+  T ("%*cX", width, '1');
+  T ("%*cXY", width, '1');        /* { dg-warning ".XY. directive output of 2 bytes causes result to exceed .INT_MAX." } */
+
+  /* Also exercise a non-constant format string.  The warning points
+     to the line where the format is declared (see bug 87773) so avoid
+     triggering that bug here.  */
+  const char *fmt = "%*cXYZ";  T (fmt, width, '1');            /* { dg-warning ".XYZ. directive output of 3 bytes causes result to exceed .INT_MAX." } */
+}
+
+
+/* Exercise the "%s" directive with constant arguments.  */
+
+void test_user_print_s_const (int width)
+{
+  const char *null = 0;
+
+  T ("%s", null);                 /* { dg-warning ".%s. directive argument is null" } */
+  T ("%.0s", null);               /* { dg-warning ".%.0s. directive argument is null" } */
+
+  /* Verify no warning is issued for unreachable code.  */
+  if (null)
+    T ("%s", null);
+
+  T ("%s", &chr_no_nul);          /* { dg-warning ".%s. directive argument is not a nul-terminated string" } */
+  T ("%s", arr_no_nul);           /* { dg-warning ".%s. directive argument is not a nul-terminated string" } */
+
+  /* Verify that output in excess of INT_MAX bytes is diagnosed even
+     when the size of the destination object is unknown.  */
+  T ("%*s",  INT_MAX - 1, "");
+  T ("%*s",  INT_MAX,     "");
+  T ("X%*s", INT_MAX,     "");    /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  if (width < INT_MAX - 1)
+    width = INT_MAX - 1;
+  T ("%*sX", width, "1");
+  T ("%*sXY", width, "1");        /* { dg-warning ".XY. directive output of 2 bytes causes result to exceed .INT_MAX." } */
+}
+
+
+const wchar_t wchr_no_nul = L'a';
+const wchar_t warr_no_nul[] = { L'a', L'b' };
+
+/* Exercise the "%s" directive with constant arguments.  */
+
+void test_user_print_ls_const (int width)
+{
+  const wchar_t *null = 0;
+
+  T ("%ls", null);                /* { dg-warning ".%ls. directive argument is null" } */
+  T ("%.0ls", null);              /* { dg-warning ".%.0ls. directive argument is null" } */
+
+  /* Verify no warning is issued for unreachable code.  */
+  if (null)
+    T ("%ls", null);
+
+  T ("%ls", &wchr_no_nul);        /* { dg-warning ".%ls. directive argument is not a nul-terminated string" } */
+  T ("%ls", warr_no_nul);         /* { dg-warning ".%ls. directive argument is not a nul-terminated string" } */
+
+  /* Verify that output in excess of INT_MAX bytes is diagnosed even
+     when the size of the destination object is unknown.  */
+  T ("%*ls",  INT_MAX - 1, L"");
+  T ("%*ls",  INT_MAX,     L"");
+  T ("X%*ls", INT_MAX,     L"");  /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  if (width < INT_MAX - 1)
+    width = INT_MAX - 1;
+  T ("%*lsX", width, L"1");
+  T ("%*lsXY", width, L"1");      /* { dg-warning ".XY. directive output of 2 bytes causes result to exceed .INT_MAX." } */
+}

Reply via email to