aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Sebor <msebor@redhat.com>2018-10-30 21:58:35 +0000
committerMartin Sebor <msebor@gcc.gnu.org>2018-10-30 15:58:35 -0600
commit91e3ec29af2b20d26a97b4b80c88eac9ad95e011 (patch)
treed20422e7e859155af3641fb4809c1ccabcd62586
parent448af20a27c9a1706712eba8500f5f81f5f6a46d (diff)
downloadgcc-91e3ec29af2b20d26a97b4b80c88eac9ad95e011.zip
gcc-91e3ec29af2b20d26a97b4b80c88eac9ad95e011.tar.gz
gcc-91e3ec29af2b20d26a97b4b80c88eac9ad95e011.tar.bz2
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. From-SVN: r265648
-rw-r--r--gcc/ChangeLog14
-rw-r--r--gcc/c-family/ChangeLog6
-rw-r--r--gcc/c-family/c-format.c7
-rw-r--r--gcc/gimple-ssa-sprintf.c190
-rw-r--r--gcc/testsuite/ChangeLog11
-rw-r--r--gcc/testsuite/gcc.c-torture/execute/fprintf-2.c53
-rw-r--r--gcc/testsuite/gcc.c-torture/execute/printf-2.c60
-rw-r--r--gcc/testsuite/gcc.c-torture/execute/user-printf.c64
-rw-r--r--gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c132
-rw-r--r--gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-2.c213
-rw-r--r--gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-warn-1.c129
-rw-r--r--gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c155
12 files changed, 1009 insertions, 25 deletions
diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index f788f5f..6cdbe51 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -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.
diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog
index ace36fb..5b96582 100644
--- a/gcc/c-family/ChangeLog
+++ b/gcc/c-family/ChangeLog
@@ -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).
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index a1133c7..dc937c6 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -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))
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 90f028d..456a7d4 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -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);
diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog
index cab5927..3d96f1c 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -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
index 0000000..bba4a44
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/execute/fprintf-2.c
@@ -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
index 0000000..5074110
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/execute/printf-2.c
@@ -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
index 0000000..e5784ed
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/execute/user-printf.c
@@ -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
index 0000000..5e193d6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c
@@ -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
index 0000000..701f61a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-2.c
@@ -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
index 0000000..31a5bd3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-printf-warn-1.c
@@ -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
index 0000000..4788b02
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/user-printf-warn-1.c
@@ -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." } */
+}