diff mbox series

avoid -Wnonnull for printf format in dead code (PR 87041)

Message ID ac902a25-779d-95c5-30f4-c0fa49e2a4e6@gmail.com
State New
Headers show
Series avoid -Wnonnull for printf format in dead code (PR 87041) | expand

Commit Message

Martin Sebor Oct. 29, 2018, 9:59 p.m. UTC
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

Comments

Jeff Law Oct. 30, 2018, 7:07 p.m. UTC | #1
On 10/29/18 3:59 PM, Martin Sebor wrote:
> 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
> 
> gcc-87041.diff
> 
> 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.
OK.

Note some folks might complain about dropping the warning from the
front-end.  Their (largely reasonable) argument is that warning out of
the front-end is stable across releases and doesn't depend on
optimizations.  Of course the downside of warning out of the front-end
is false positives like we see in this PR.

jeff
Christophe Lyon Nov. 2, 2018, 9:54 a.m. UTC | #2
On Tue, 30 Oct 2018 at 20:07, Jeff Law <law@redhat.com> wrote:
>
> On 10/29/18 3:59 PM, Martin Sebor wrote:
> > 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
> >
> > gcc-87041.diff
> >
> > 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.
> OK.
>

Hi,

I've noticed failure on targets using newlib (aarch64-elf and arm-eabi):
FAIL: gcc.c-torture/execute/printf-2.c
FAIL: gcc.c-torture/execute/user-printf.c

my gcc.log contains:
gcc.c-torture/execute/user-printf.c   -O0  execution test (reason: TCL
LOOKUP CHANNEL exp5)
which is not very helpful


> Note some folks might complain about dropping the warning from the
> front-end.  Their (largely reasonable) argument is that warning out of
> the front-end is stable across releases and doesn't depend on
> optimizations.  Of course the downside of warning out of the front-end
> is false positives like we see in this PR.
>
> jeff
Matthew Malcomson Nov. 15, 2018, 9:39 a.m. UTC | #3
On 02/11/18 09:54, Christophe Lyon wrote:
> Hi,
>
> I've noticed failure on targets using newlib (aarch64-elf and arm-eabi):
> FAIL: gcc.c-torture/execute/printf-2.c
> FAIL: gcc.c-torture/execute/user-printf.c
>
> my gcc.log contains:
> gcc.c-torture/execute/user-printf.c   -O0  execution test (reason: TCL
> LOOKUP CHANNEL exp5)
> which is not very helpful
>

@Christophe
Can I ask if your DejaGNU board setup has "needs_status_wrapper 1" for 
both of these boards?

I believe this problem is caused by an interaction with the DejaGNU 
status wrapper.

When the status wrapper is needed, DejaGNU looks at stdout for a line 
saying "*** EXIT code <number>" indicating what the status code was.
When it doesn't find that line it assumes an exit code of 2.
Without the status wrapper DejaGNU takes the return code from the 
program executed.

Because these tests use "freopen ()" on stdout, the status wrapper fails 
to print to the IO channel DejaGNU is listening on, hence DejaGNU fails 
to find it's line, uses an exit code of 2, and fails the test.


@Martin
Would these tests still be valid having run freopen on stderr and using 
fprintf instead of printf?
That makes the testcases pass for me.

If not we could add an
    { dg-require-effective-target unwrapped }
directive in the testcases to stop the failure complaints.
Christophe Lyon Nov. 15, 2018, 10:15 a.m. UTC | #4
On Thu, 15 Nov 2018 at 10:39, Matthew Malcomson
<Matthew.Malcomson@arm.com> wrote:
>
> On 02/11/18 09:54, Christophe Lyon wrote:
> > Hi,
> >
> > I've noticed failure on targets using newlib (aarch64-elf and arm-eabi):
> > FAIL: gcc.c-torture/execute/printf-2.c
> > FAIL: gcc.c-torture/execute/user-printf.c
> >
> > my gcc.log contains:
> > gcc.c-torture/execute/user-printf.c   -O0  execution test (reason: TCL
> > LOOKUP CHANNEL exp5)
> > which is not very helpful
> >
>
> @Christophe
> Can I ask if your DejaGNU board setup has "needs_status_wrapper 1" for
> both of these boards?
>
Yes, I do use this.

> I believe this problem is caused by an interaction with the DejaGNU
> status wrapper.
>
> When the status wrapper is needed, DejaGNU looks at stdout for a line
> saying "*** EXIT code <number>" indicating what the status code was.
> When it doesn't find that line it assumes an exit code of 2.
> Without the status wrapper DejaGNU takes the return code from the
> program executed.
>
> Because these tests use "freopen ()" on stdout, the status wrapper fails
> to print to the IO channel DejaGNU is listening on, hence DejaGNU fails
> to find it's line, uses an exit code of 2, and fails the test.
>
>
> @Martin
> Would these tests still be valid having run freopen on stderr and using
> fprintf instead of printf?
> That makes the testcases pass for me.
>
> If not we could add an
>     { dg-require-effective-target unwrapped }
> directive in the testcases to stop the failure complaints.
Martin Sebor Nov. 15, 2018, 7:06 p.m. UTC | #5
On 11/15/2018 02:39 AM, Matthew Malcomson wrote:
> On 02/11/18 09:54, Christophe Lyon wrote:
>> Hi,
>>
>> I've noticed failure on targets using newlib (aarch64-elf and arm-eabi):
>> FAIL: gcc.c-torture/execute/printf-2.c
>> FAIL: gcc.c-torture/execute/user-printf.c
>>
>> my gcc.log contains:
>> gcc.c-torture/execute/user-printf.c   -O0  execution test (reason: TCL
>> LOOKUP CHANNEL exp5)
>> which is not very helpful
>>
>
> @Christophe
> Can I ask if your DejaGNU board setup has "needs_status_wrapper 1" for
> both of these boards?
>
> I believe this problem is caused by an interaction with the DejaGNU
> status wrapper.
>
> When the status wrapper is needed, DejaGNU looks at stdout for a line
> saying "*** EXIT code <number>" indicating what the status code was.
> When it doesn't find that line it assumes an exit code of 2.
> Without the status wrapper DejaGNU takes the return code from the
> program executed.
>
> Because these tests use "freopen ()" on stdout, the status wrapper fails
> to print to the IO channel DejaGNU is listening on, hence DejaGNU fails
> to find it's line, uses an exit code of 2, and fails the test.
>
>
> @Martin
> Would these tests still be valid having run freopen on stderr and using
> fprintf instead of printf?
> That makes the testcases pass for me.

The printf-2.c test specifically exercises the printf built-in,
i.e., not fprintf, so changing it to use fprintf would defeat
its purpose.

user-printf.c does something similar but for a user-defined
function with attribute format (printf).

The purpose of the tests is to verify that what they read from
the temporary file matches what they wrote (i.e,, that none of
the printf() or user_print() calls was incorrectly eliminated).

> If not we could add an
>     { dg-require-effective-target unwrapped }
> directive in the testcases to stop the failure complaints.

I'm not familiar with this directive or really know what
a status wrapper is but as long as it doesn't change the I/O
the test does I think it should be fine.

Martin
Jeff Law Nov. 16, 2018, 4:04 p.m. UTC | #6
On 11/15/18 12:06 PM, Martin Sebor wrote:
> On 11/15/2018 02:39 AM, Matthew Malcomson wrote:
>> On 02/11/18 09:54, Christophe Lyon wrote:
>>> Hi,
>>>
>>> I've noticed failure on targets using newlib (aarch64-elf and arm-eabi):
>>> FAIL: gcc.c-torture/execute/printf-2.c
>>> FAIL: gcc.c-torture/execute/user-printf.c
>>>
>>> my gcc.log contains:
>>> gcc.c-torture/execute/user-printf.c   -O0  execution test (reason: TCL
>>> LOOKUP CHANNEL exp5)
>>> which is not very helpful
>>>
>>
>> @Christophe
>> Can I ask if your DejaGNU board setup has "needs_status_wrapper 1" for
>> both of these boards?
>>
>> I believe this problem is caused by an interaction with the DejaGNU
>> status wrapper.
>>
>> When the status wrapper is needed, DejaGNU looks at stdout for a line
>> saying "*** EXIT code <number>" indicating what the status code was.
>> When it doesn't find that line it assumes an exit code of 2.
>> Without the status wrapper DejaGNU takes the return code from the
>> program executed.
>>
>> Because these tests use "freopen ()" on stdout, the status wrapper fails
>> to print to the IO channel DejaGNU is listening on, hence DejaGNU fails
>> to find it's line, uses an exit code of 2, and fails the test.
>>
>>
>> @Martin
>> Would these tests still be valid having run freopen on stderr and using
>> fprintf instead of printf?
>> That makes the testcases pass for me.
> 
> The printf-2.c test specifically exercises the printf built-in,
> i.e., not fprintf, so changing it to use fprintf would defeat
> its purpose.
> 
> user-printf.c does something similar but for a user-defined
> function with attribute format (printf).
> 
> The purpose of the tests is to verify that what they read from
> the temporary file matches what they wrote (i.e,, that none of
> the printf() or user_print() calls was incorrectly eliminated).
> 
>> If not we could add an
>>     { dg-require-effective-target unwrapped }
>> directive in the testcases to stop the failure complaints.
> 
> I'm not familiar with this directive or really know what
> a status wrapper is but as long as it doesn't change the I/O
> the test does I think it should be fine.
Wrapping in this context refers to the dejagnu harness wrapping to
facilitate testing of remote and embedded targets where getting the real
exit status of an execution test is painful.

We wrap main, exit and abort.  The wrappers print info to stdout to
indicate exit status which can reliably be read by the harness.

At least that's my recollection of the wrapper bits.

jeff
diff mbox series

Patch

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." } */
+}