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
This commit is contained in:
Martin Sebor 2018-10-30 21:58:35 +00:00 committed by Martin Sebor
parent 448af20a27
commit 91e3ec29af
12 changed files with 1009 additions and 25 deletions

View file

@ -1,3 +1,17 @@
2018-10-30 Martin Sebor <msebor@redhat.com>
PR middle-end/87041
* gimple-ssa-sprintf.c (format_directive): Use %G to include
inlining context.
(sprintf_dom_walker::compute_format_length):
Avoid setting POSUNDER4K here.
(get_destination_size): Handle null argument values.
(get_user_idx_format): New function.
(sprintf_dom_walker::handle_gimple_call): Handle all printf-like
functions, including user-defined with attribute format printf.
Use %G to include inlining context.
Set POSUNDER4K here.
2018-10-30 Jan Hubicka <jh@suse.cz>
* params.def (lto-partitions): Bump from 32 to 128.

View file

@ -1,3 +1,9 @@
2018-10-30 Martin Sebor <msebor@redhat.com>
PR middle-end/87041
* c-format.c (check_format_types): Avoid diagnosing null pointer
arguments to printf-family of functions.
2018-10-30 Marek Polacek <polacek@redhat.com>
Implement P0892R2, explicit(bool).

View file

@ -3123,8 +3123,11 @@ check_format_types (const substring_loc &fmt_loc,
warning (OPT_Wformat_, "writing through null pointer "
"(argument %d)", arg_num);
/* Check for reading through a NULL pointer. */
if (types->reading_from_flag
/* Check for reading through a NULL pointer. Ignore
printf-family of functions as they are checked for
null arguments by the middle-end. */
if (fki->conversion_specs != print_char_table
&& types->reading_from_flag
&& i == 0
&& cur_param != 0
&& integer_zerop (cur_param))

View file

@ -68,6 +68,7 @@ along with GCC; see the file COPYING3. If not see
#include "intl.h"
#include "langhooks.h"
#include "attribs.h"
#include "builtins.h"
#include "stor-layout.h"
@ -2796,8 +2797,9 @@ format_directive (const sprintf_dom_walker::call_info &info,
if (fmtres.nullp)
{
fmtwarn (dirloc, argloc, NULL, info.warnopt (),
"%<%.*s%> directive argument is null",
dirlen, target_to_host (hostdir, sizeof hostdir, dir.beg));
"%G%<%.*s%> directive argument is null",
info.callstmt, dirlen,
target_to_host (hostdir, sizeof hostdir, dir.beg));
/* Don't bother processing the rest of the format string. */
res->warned = true;
@ -3475,7 +3477,6 @@ sprintf_dom_walker::compute_format_length (call_info &info,
by the known range [0, 0] (with no conversion resulting in a failure
or producing more than 4K bytes) until determined otherwise. */
res->knownrange = true;
res->posunder4k = true;
res->floating = false;
res->warned = false;
@ -3518,6 +3519,10 @@ sprintf_dom_walker::compute_format_length (call_info &info,
static unsigned HOST_WIDE_INT
get_destination_size (tree dest)
{
/* When there is no destination return -1. */
if (!dest)
return HOST_WIDE_INT_M1U;
/* Initialize object size info before trying to compute it. */
init_object_sizes ();
@ -3738,6 +3743,37 @@ try_simplify_call (gimple_stmt_iterator *gsi,
return false;
}
/* Return the zero-based index of the format string argument of a printf
like function and set *IDX_ARGS to the first format argument. When
no such index exists return UINT_MAX. */
static unsigned
get_user_idx_format (tree fndecl, unsigned *idx_args)
{
tree attrs = lookup_attribute ("format", DECL_ATTRIBUTES (fndecl));
if (!attrs)
attrs = lookup_attribute ("format", TYPE_ATTRIBUTES (TREE_TYPE (fndecl)));
if (!attrs)
return UINT_MAX;
attrs = TREE_VALUE (attrs);
tree archetype = TREE_VALUE (attrs);
if (strcmp ("printf", IDENTIFIER_POINTER (archetype)))
return UINT_MAX;
attrs = TREE_CHAIN (attrs);
tree fmtarg = TREE_VALUE (attrs);
attrs = TREE_CHAIN (attrs);
tree elliparg = TREE_VALUE (attrs);
/* Attribute argument indices are 1-based but we use zero-based. */
*idx_args = tree_to_uhwi (elliparg) - 1;
return tree_to_uhwi (fmtarg) - 1;
}
/* Determine if a GIMPLE CALL is to one of the sprintf-like built-in
functions and if so, handle it. Return true if the call is removed
and gsi_next should not be performed in the caller. */
@ -3748,29 +3784,93 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
call_info info = call_info ();
info.callstmt = gsi_stmt (*gsi);
if (!gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
info.func = gimple_call_fndecl (info.callstmt);
if (!info.func)
return false;
info.func = gimple_call_fndecl (info.callstmt);
info.fncode = DECL_FUNCTION_CODE (info.func);
/* Format string argument number (valid for all functions). */
unsigned idx_format = UINT_MAX;
if (!gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
{
unsigned idx_args;
idx_format = get_user_idx_format (info.func, &idx_args);
if (idx_format == UINT_MAX)
return false;
info.argidx = idx_args;
}
/* The size of the destination as in snprintf(dest, size, ...). */
unsigned HOST_WIDE_INT dstsize = HOST_WIDE_INT_M1U;
/* The size of the destination determined by __builtin_object_size. */
unsigned HOST_WIDE_INT objsize = HOST_WIDE_INT_M1U;
/* Buffer size argument number (snprintf and vsnprintf). */
unsigned HOST_WIDE_INT idx_dstsize = HOST_WIDE_INT_M1U;
/* Zero-based buffer size argument number (snprintf and vsnprintf). */
unsigned idx_dstsize = UINT_MAX;
/* Object size argument number (snprintf_chk and vsnprintf_chk). */
unsigned HOST_WIDE_INT idx_objsize = HOST_WIDE_INT_M1U;
unsigned idx_objsize = UINT_MAX;
/* Format string argument number (valid for all functions). */
unsigned idx_format;
/* Destinaton argument number (valid for sprintf functions only). */
unsigned idx_dstptr = 0;
switch (info.fncode)
{
case BUILT_IN_NONE:
// User-defined function with attribute format (printf).
idx_dstptr = -1;
break;
case BUILT_IN_FPRINTF:
// Signature:
// __builtin_fprintf (FILE*, format, ...)
idx_format = 1;
info.argidx = 2;
idx_dstptr = -1;
break;
case BUILT_IN_FPRINTF_CHK:
// Signature:
// __builtin_fprintf_chk (FILE*, ost, format, ...)
idx_format = 2;
info.argidx = 3;
idx_dstptr = -1;
break;
case BUILT_IN_FPRINTF_UNLOCKED:
// Signature:
// __builtin_fprintf_unnlocked (FILE*, format, ...)
idx_format = 1;
info.argidx = 2;
idx_dstptr = -1;
break;
case BUILT_IN_PRINTF:
// Signature:
// __builtin_printf (format, ...)
idx_format = 0;
info.argidx = 1;
idx_dstptr = -1;
break;
case BUILT_IN_PRINTF_CHK:
// Signature:
// __builtin_printf_chk (it, format, ...)
idx_format = 1;
info.argidx = 2;
idx_dstptr = -1;
break;
case BUILT_IN_PRINTF_UNLOCKED:
// Signature:
// __builtin_printf (format, ...)
idx_format = 0;
info.argidx = 1;
idx_dstptr = -1;
break;
case BUILT_IN_SPRINTF:
// Signature:
// __builtin_sprintf (dst, format, ...)
@ -3805,6 +3905,38 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
info.bounded = true;
break;
case BUILT_IN_VFPRINTF:
// Signature:
// __builtin_vprintf (FILE*, format, va_list)
idx_format = 1;
info.argidx = -1;
idx_dstptr = -1;
break;
case BUILT_IN_VFPRINTF_CHK:
// Signature:
// __builtin___vfprintf_chk (FILE*, ost, format, va_list)
idx_format = 2;
info.argidx = -1;
idx_dstptr = -1;
break;
case BUILT_IN_VPRINTF:
// Signature:
// __builtin_vprintf (format, va_list)
idx_format = 0;
info.argidx = -1;
idx_dstptr = -1;
break;
case BUILT_IN_VPRINTF_CHK:
// Signature:
// __builtin___vprintf_chk (ost, format, va_list)
idx_format = 1;
info.argidx = -1;
idx_dstptr = -1;
break;
case BUILT_IN_VSNPRINTF:
// Signature:
// __builtin_vsprintf (dst, size, format, va)
@ -3846,8 +3978,10 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
/* Set the global warning level for this function. */
warn_level = info.bounded ? warn_format_trunc : warn_format_overflow;
/* The first argument is a pointer to the destination. */
tree dstptr = gimple_call_arg (info.callstmt, 0);
/* For all string functions the first argument is a pointer to
the destination. */
tree dstptr = (idx_dstptr < gimple_call_num_args (info.callstmt)
? gimple_call_arg (info.callstmt, 0) : NULL_TREE);
info.format = gimple_call_arg (info.callstmt, idx_format);
@ -3855,7 +3989,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
or upper bound of a range. */
bool dstsize_cst_p = true;
if (idx_dstsize == HOST_WIDE_INT_M1U)
if (idx_dstsize == UINT_MAX)
{
/* For non-bounded functions like sprintf, determine the size
of the destination from the object or pointer passed to it
@ -3880,7 +4014,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
/* Avoid warning if -Wstringop-overflow is specified since
it also warns for the same thing though only for the
checking built-ins. */
if ((idx_objsize == HOST_WIDE_INT_M1U
if ((idx_objsize == UINT_MAX
|| !warn_stringop_overflow))
warning_at (gimple_location (info.callstmt), info.warnopt (),
"specified bound %wu exceeds maximum object size "
@ -3910,7 +4044,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
}
}
if (idx_objsize != HOST_WIDE_INT_M1U)
if (idx_objsize != UINT_MAX)
if (tree size = gimple_call_arg (info.callstmt, idx_objsize))
if (tree_fits_uhwi_p (size))
objsize = tree_to_uhwi (size);
@ -3930,14 +4064,15 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
/* For calls to non-bounded functions or to those of bounded
functions with a non-zero size, warn if the destination
pointer is null. */
if (integer_zerop (dstptr))
if (dstptr && integer_zerop (dstptr))
{
/* This is diagnosed with -Wformat only when the null is a constant
pointer. The warning here diagnoses instances where the pointer
is not constant. */
location_t loc = gimple_location (info.callstmt);
warning_at (EXPR_LOC_OR_LOC (dstptr, loc),
info.warnopt (), "null destination pointer");
info.warnopt (), "%Gnull destination pointer",
info.callstmt);
return false;
}
@ -3950,7 +4085,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
/* Avoid warning if -Wstringop-overflow is specified since
it also warns for the same thing though only for the
checking built-ins. */
&& (idx_objsize == HOST_WIDE_INT_M1U
&& (idx_objsize == UINT_MAX
|| !warn_stringop_overflow))
{
warning_at (gimple_location (info.callstmt), info.warnopt (),
@ -3959,14 +4094,15 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
}
}
if (integer_zerop (info.format))
/* Determine if the format argument may be null and warn if not
and if the argument is null. */
if (integer_zerop (info.format)
&& gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
{
/* This is diagnosed with -Wformat only when the null is a constant
pointer. The warning here diagnoses instances where the pointer
is not constant. */
location_t loc = gimple_location (info.callstmt);
warning_at (EXPR_LOC_OR_LOC (info.format, loc),
info.warnopt (), "null format string");
info.warnopt (), "%Gnull format string",
info.callstmt);
return false;
}
@ -3978,6 +4114,14 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
including the terminating NUL. */
format_result res = format_result ();
/* I/O functions with no destination argument (i.e., all forms of fprintf
and printf) may fail under any conditions. Others (i.e., all forms of
sprintf) may only fail under specific conditions determined for each
directive. Clear POSUNDER4K for the former set of functions and set
it to true for the latter (it can only be cleared later, but it is
never set to true again). */
res.posunder4k = dstptr;
bool success = compute_format_length (info, &res);
if (res.warned)
gimple_set_no_warning (info.callstmt, true);

View file

@ -1,3 +1,14 @@
2018-10-30 Martin Sebor <msebor@redhat.com>
PR middle-end/87041
* gcc.c-torture/execute/fprintf-2.c: New test.
* gcc.c-torture/execute/printf-2.c: Same.
* gcc.c-torture/execute/user-printf.c: Same.
* gcc.dg/tree-ssa/builtin-fprintf-warn-1.c: Same.
* gcc.dg/tree-ssa/builtin-printf-2.c: Same.
* gcc.dg/tree-ssa/builtin-printf-warn-1.c: Same.
* gcc.dg/tree-ssa/user-printf-warn-1.c: Same.
2018-10-30 Marek Polacek <polacek@redhat.com>
Implement P0892R2, explicit(bool).

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

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

View file

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

View file

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

View file

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