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