]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
PR middle-end/87041 - -Wformat reading through null pointer on unreachable code
authorMartin Sebor <msebor@redhat.com>
Tue, 30 Oct 2018 21:58:35 +0000 (21:58 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Tue, 30 Oct 2018 21:58:35 +0000 (15:58 -0600)
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.

From-SVN: r265648

12 files changed:
gcc/ChangeLog
gcc/c-family/ChangeLog
gcc/c-family/c-format.c
gcc/gimple-ssa-sprintf.c
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.c-torture/execute/fprintf-2.c [new file with mode: 0644]
gcc/testsuite/gcc.c-torture/execute/printf-2.c [new file with mode: 0644]
gcc/testsuite/gcc.c-torture/execute/user-printf.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-warn-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c [new file with mode: 0644]

index f788f5f2a18b9ecdd1568a3faa7401c3a561eede..6cdbe51ce752f628e8777dc8ea8fdeeba87d7c85 100644 (file)
@@ -1,3 +1,17 @@
+2018-10-30  Martin Sebor  <msebor@redhat.com>
+
+       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.
+
 2018-10-30  Jan Hubicka  <jh@suse.cz>
 
        * params.def (lto-partitions): Bump from 32 to 128.
index ace36fb30e8d2116763c96528dd3bc10a5e58c1d..5b9658256200c5b54e5be8a06ac0a2c678410b56 100644 (file)
@@ -1,3 +1,9 @@
+2018-10-30  Martin Sebor  <msebor@redhat.com>
+
+       PR middle-end/87041
+       * c-format.c (check_format_types): Avoid diagnosing null pointer
+       arguments to printf-family of functions.
+
 2018-10-30  Marek Polacek  <polacek@redhat.com>
 
        Implement P0892R2, explicit(bool).
index a1133c75d93d1e7176f4abf722b49546e6db96ae..dc937c6cabafd732c768ce46589437f96a4c1288 100644 (file)
@@ -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 90f028d50bdbf48dbaf4adb4c6e89aae175ac33c..456a7d400115713a6600b1ce7bb303b6c971550e 100644 (file)
@@ -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_info &info,
   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_info &info,
      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_info &info,
 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,29 +3784,93 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
   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;
 
   /* 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_stmt_iterator *gsi)
       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_stmt_iterator *gsi)
   /* 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_stmt_iterator *gsi)
      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_stmt_iterator *gsi)
              /* 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_stmt_iterator *gsi)
        }
     }
 
-  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,14 +4064,15 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
       /* 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
             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_stmt_iterator *gsi)
          /* 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_stmt_iterator *gsi)
        }
     }
 
-  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_stmt_iterator *gsi)
      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 cab592771963b921f41cace0983e141333356426..3d96f1caaa006c4772309bcf2e40b25fcc5615d1 100644 (file)
@@ -1,3 +1,14 @@
+2018-10-30  Martin Sebor  <msebor@redhat.com>
+
+       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.
+
 2018-10-30  Marek Polacek  <polacek@redhat.com>
 
        Implement P0892R2, explicit(bool).
diff --git a/gcc/testsuite/gcc.c-torture/execute/fprintf-2.c b/gcc/testsuite/gcc.c-torture/execute/fprintf-2.c
new file mode 100644 (file)
index 0000000..bba4a44
--- /dev/null
@@ -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;
+}
diff --git a/gcc/testsuite/gcc.c-torture/execute/printf-2.c b/gcc/testsuite/gcc.c-torture/execute/printf-2.c
new file mode 100644 (file)
index 0000000..5074110
--- /dev/null
@@ -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;
+}
diff --git a/gcc/testsuite/gcc.c-torture/execute/user-printf.c b/gcc/testsuite/gcc.c-torture/execute/user-printf.c
new file mode 100644 (file)
index 0000000..e5784ed
--- /dev/null
@@ -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;
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c
new file mode 100644 (file)
index 0000000..5e193d6
--- /dev/null
@@ -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" "pr87756" { xfail *-*-* } } */
+  T ("%s", arr_no_nul);           /* { dg-warning ".%s. directive argument is not a nul-terminated string" "pr87756" { xfail *-*-* } } */
+
+  /* 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" "pr87756" { xfail *-*-* } } */
+  T ("%ls", warr_no_nul);         /* { dg-warning ".%ls. directive argument is not a nul-terminated string" "pr87756" { xfail *-*-* } } */
+
+  /* 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." } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-2.c
new file mode 100644 (file)
index 0000000..701f61a
--- /dev/null
@@ -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"} } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-warn-1.c
new file mode 100644 (file)
index 0000000..31a5bd3
--- /dev/null
@@ -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" "pr87756" { xfail *-*-* } } */
+  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" "pr87756" { xfail *-*-* } } */
+  T ("%ls", warr_no_nul);         /* { dg-warning ".%ls. directive argument is not a nul-terminated string" "pr87756" { xfail *-*-* } } */
+
+  /* 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." } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c
new file mode 100644 (file)
index 0000000..4788b02
--- /dev/null
@@ -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 user-defined function declared attribute format printf.
+   { 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" "pr87756" { xfail *-*-* } } */
+  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" "pr87756" { xfail *-*-* } } */
+  T ("%ls", warr_no_nul);         /* { dg-warning ".%ls. directive argument is not a nul-terminated string" "pr87756" { xfail *-*-* } } */
+
+  /* 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." } */
+}