From 88d0c3f0a1448e71dcf49c2f34909ec8d7ce348f Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Wed, 21 Sep 2016 01:39:27 +0000 Subject: [PATCH] PR middle-end/49905 - Better sanity checking on sprintf src & dest to gcc/ChangeLog: PR middle-end/49905 * Makefile.in (OBJS): Add gimple-ssa-sprintf.o. * config/linux.h (TARGET_PRINTF_POINTER_FORMAT): Redefine. * config/linux.c (gnu_libc_printf_pointer_format): New function. * config/sol2.h (TARGET_PRINTF_POINTER_FORMAT): Same. * config/sol2.c (solaris_printf_pointer_format): New function. * doc/invoke.texi (-Wformat-length, -fprintf-return-value): New options. * doc/tm.texi.in (TARGET_PRINTF_POINTER_FORMAT): Document. * doc/tm.texi: Regenerate. * gimple-fold.h (get_range_strlen): New function. (get_maxval_strlen): Declare existing function. * gimple-fold.c (get_range_strlen): Add arguments and compute both maximum and minimum. (get_range_strlen): Define overload. (get_maxval_strlen): Adjust. * gimple-ssa-sprintf.c: New file and pass. * passes.def (pass_sprintf_length): Add new pass. * targhooks.h (default_printf_pointer_format): Declare new function. (gnu_libc_printf_pointer_format): Same. (solaris_libc_printf_pointer_format): Same. * targhooks.c (default_printf_pointer_format): Define new function. * tree-pass.h (make_pass_sprintf_length): Declare new function. * print-tree.c: Increase buffer size. gcc/c-family/ChangeLog: PR middle-end/49905 * c.opt: Add -Wformat-length and -fprintf-return-value. gcc/testsuite/ChangeLog: PR middle-end/49905 * gcc.dg/builtin-stringop-chk-1.c: Adjust. * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: New test. * gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: New test. * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: New test. * gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: New test. * gcc.dg/tree-ssa/builtin-sprintf.c: New test. * gcc.dg/tree-ssa/builtin-sprintf-2.c: New test. From-SVN: r240298 --- gcc/ChangeLog | 27 + gcc/Makefile.in | 1 + gcc/c-family/ChangeLog | 5 + gcc/c-family/c.opt | 14 + gcc/config/linux.c | 17 + gcc/config/linux.h | 4 + gcc/config/sol2.c | 14 + gcc/config/sol2.h | 4 + gcc/doc/invoke.texi | 111 +- gcc/doc/tm.texi | 4 + gcc/doc/tm.texi.in | 2 + gcc/gimple-fold.c | 123 +- gcc/gimple-fold.h | 2 + gcc/gimple-ssa-sprintf.c | 2686 +++++++++++++++++ gcc/passes.def | 2 + gcc/print-tree.c | 3 +- gcc/target.def | 6 + gcc/targhooks.c | 14 + gcc/targhooks.h | 4 + gcc/testsuite/ChangeLog | 11 + gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c | 2 +- .../gcc.dg/tree-ssa/builtin-sprintf-2.c | 218 ++ .../gcc.dg/tree-ssa/builtin-sprintf-warn-1.c | 1417 +++++++++ .../gcc.dg/tree-ssa/builtin-sprintf-warn-2.c | 214 ++ .../gcc.dg/tree-ssa/builtin-sprintf-warn-3.c | 234 ++ .../gcc.dg/tree-ssa/builtin-sprintf-warn-4.c | 33 + .../gcc.dg/tree-ssa/builtin-sprintf.c | 540 ++++ gcc/tree-pass.h | 1 + 28 files changed, 5683 insertions(+), 30 deletions(-) create mode 100644 gcc/gimple-ssa-sprintf.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 4f2f973c1d3..8ccafc548a0 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,30 @@ +2016-09-20 Martin Sebor + + PR middle-end/49905 + * Makefile.in (OBJS): Add gimple-ssa-sprintf.o. + * config/linux.h (TARGET_PRINTF_POINTER_FORMAT): Redefine. + * config/linux.c (gnu_libc_printf_pointer_format): New function. + * config/sol2.h (TARGET_PRINTF_POINTER_FORMAT): Same. + * config/sol2.c (solaris_printf_pointer_format): New function. + * doc/invoke.texi (-Wformat-length, -fprintf-return-value): New + options. + * doc/tm.texi.in (TARGET_PRINTF_POINTER_FORMAT): Document. + * doc/tm.texi: Regenerate. + * gimple-fold.h (get_range_strlen): New function. + (get_maxval_strlen): Declare existing function. + * gimple-fold.c (get_range_strlen): Add arguments and compute both + maximum and minimum. + (get_range_strlen): Define overload. + (get_maxval_strlen): Adjust. + * gimple-ssa-sprintf.c: New file and pass. + * passes.def (pass_sprintf_length): Add new pass. + * targhooks.h (default_printf_pointer_format): Declare new function. + (gnu_libc_printf_pointer_format): Same. + (solaris_libc_printf_pointer_format): Same. + * targhooks.c (default_printf_pointer_format): Define new function. + * tree-pass.h (make_pass_sprintf_length): Declare new function. + * print-tree.c: Increase buffer size. + 2016-09-21 Kugan Vivekanandarajah * tree-vrp.c (get_value_range): Teach PARM_DECL to use ipa-vrp diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 332c85e2fb2..69ff9fa988d 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1296,6 +1296,7 @@ OBJS = \ gimple-ssa-nonnull-compare.o \ gimple-ssa-split-paths.o \ gimple-ssa-strength-reduction.o \ + gimple-ssa-sprintf.o \ gimple-streamer-in.o \ gimple-streamer-out.o \ gimple-walk.o \ diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog index 5efbf55e1a7..da3fdd43aad 100644 --- a/gcc/c-family/ChangeLog +++ b/gcc/c-family/ChangeLog @@ -1,3 +1,8 @@ +2016-09-20 Martin Sebor + + PR middle-end/49905 + * c.opt: Add -Wformat-length and -fprintf-return-value. + 2016-09-19 Bernd Edlinger PR c++/77434 diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 6cf915d5143..d01052d44e0 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -478,6 +478,11 @@ Wformat-extra-args C ObjC C++ ObjC++ Var(warn_format_extra_args) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0) Warn if passing too many arguments to a function for its format string. +Wformat-length +C ObjC C++ ObjC++ Warning Alias(Wformat-length=, 1, 0) +Warn about function calls with format strings that write past the end +of the destination region. Same as -Wformat-length=1. + Wformat-nonliteral C ObjC C++ ObjC++ Var(warn_format_nonliteral) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 2, 0) Warn about format strings that are not literals. @@ -502,6 +507,11 @@ Wformat= C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 1, 0) Warn about printf/scanf/strftime/strfmon format string anomalies. +Wformat-length= +C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format_length) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0) +Warn about function calls with format strings that write past the end +of the destination region. + Wignored-qualifiers C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra) Warn whenever type qualifiers are ignored. @@ -1487,6 +1497,10 @@ fpretty-templates C++ ObjC++ Var(flag_pretty_templates) Init(1) -fno-pretty-templates Do not pretty-print template specializations as the template signature followed by the arguments. +fprintf-return-value +C ObjC C++ ObjC++ LTO Optimization Var(flag_printf_return_value) Init(1) +Treat known sprintf return values as constants. + freplace-objc-classes ObjC ObjC++ LTO Var(flag_replace_objc_classes) Used in Fix-and-Continue mode to indicate that object files may be swapped in at runtime. diff --git a/gcc/config/linux.c b/gcc/config/linux.c index 16c37683b7d..9aac38b8658 100644 --- a/gcc/config/linux.c +++ b/gcc/config/linux.c @@ -21,8 +21,12 @@ along with GCC; see the file COPYING3. If not see #include "system.h" #include "coretypes.h" #include "tm.h" +#include "tree.h" #include "linux-protos.h" +#undef TARGET_PRINTF_POINTER_FORMAT +#define TARGET_PRINTF_POINTER_FORMAT gnu_libc_printf_pointer_format + bool linux_libc_has_function (enum function_class fn_class) { @@ -36,3 +40,16 @@ linux_libc_has_function (enum function_class fn_class) return false; } + +/* Glibc formats pointers as if by "%zx" except for the null pointer + which outputs "(nil)". It ignores the pound ('#') format flag but + interprets the space and plus flags the same as in the integer + directive. */ + +const char* +gnu_libc_printf_pointer_format (tree arg, const char **flags) +{ + *flags = " +"; + + return arg && integer_zerop (arg) ? "(nil)" : "%#zx"; +} diff --git a/gcc/config/linux.h b/gcc/config/linux.h index 9aeeb948f55..3ff005b90a2 100644 --- a/gcc/config/linux.h +++ b/gcc/config/linux.h @@ -208,3 +208,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see # define TARGET_LIBC_HAS_FUNCTION linux_libc_has_function #endif + +/* The format string to which "%p" corresponds. */ +#undef TARGET_PRINTF_POINTER_FORMAT +#define TARGET_PRINTF_POINTER_FORMAT gnu_libc_printf_pointer_format diff --git a/gcc/config/sol2.c b/gcc/config/sol2.c index 47b41fd1408..30c525acbc3 100644 --- a/gcc/config/sol2.c +++ b/gcc/config/sol2.c @@ -30,6 +30,9 @@ along with GCC; see the file COPYING3. If not see #include "varasm.h" #include "output.h" +#undef TARGET_PRINTF_POINTER_FORMAT +#define TARGET_PRINTF_POINTER_FORMAT solaris_printf_pointer_format + tree solaris_pending_aligns, solaris_pending_inits, solaris_pending_finis; /* Attach any pending attributes for DECL to the list in *ATTRIBUTES. @@ -297,3 +300,14 @@ solaris_override_options (void) if (!HAVE_LD_EH_FRAME_CIEV3 && !global_options_set.x_dwarf_version) dwarf_version = 2; } + +/* Solaris libc formats pointers as if by "%zx" with the pound ('#') + format flag having the same meaning as in the integer directive. */ + +const char* +solaris_printf_pointer_format (tree, const char **flags) +{ + *flags = "#"; + + return "%zx"; +} diff --git a/gcc/config/sol2.h b/gcc/config/sol2.h index 50f2b383a1b..6f0270891f5 100644 --- a/gcc/config/sol2.h +++ b/gcc/config/sol2.h @@ -440,6 +440,10 @@ along with GCC; see the file COPYING3. If not see #undef TARGET_LIBC_HAS_FUNCTION #define TARGET_LIBC_HAS_FUNCTION default_libc_has_function +/* The format string to which "%p" corresponds. */ +#undef TARGET_LIBC_PRINTF_POINTER_FORMAT +#define TARGET_LIBC_PRINTF_POINTER_FORMAT solaris_libc_printf_pointer_format + extern GTY(()) tree solaris_pending_aligns; extern GTY(()) tree solaris_pending_inits; extern GTY(()) tree solaris_pending_finis; diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 05566ba62a0..cfba069cf2b 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -268,7 +268,8 @@ Objective-C and Objective-C++ Dialects}. -Wno-div-by-zero -Wdouble-promotion -Wduplicated-cond @gol -Wempty-body -Wenum-compare -Wno-endif-labels @gol -Werror -Werror=* -Wfatal-errors -Wfloat-equal -Wformat -Wformat=2 @gol --Wno-format-contains-nul -Wno-format-extra-args -Wformat-nonliteral @gol +-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=@var{n} @gol +-Wformat-nonliteral @gol -Wformat-security -Wformat-signedness -Wformat-y2k -Wframe-address @gol -Wframe-larger-than=@var{len} -Wno-free-nonheap-object -Wjump-misses-init @gol -Wignored-qualifiers -Wignored-attributes -Wincompatible-pointer-types @gol @@ -379,7 +380,7 @@ Objective-C and Objective-C++ Dialects}. -fno-toplevel-reorder -fno-trapping-math -fno-zero-initialized-in-bss @gol -fomit-frame-pointer -foptimize-sibling-calls @gol -fpartial-inlining -fpeel-loops -fpredictive-commoning @gol --fprefetch-loop-arrays @gol +-fprefetch-loop-arrays -fprintf-return-value @gol -fprofile-correction @gol -fprofile-use -fprofile-use=@var{path} -fprofile-values @gol -fprofile-reorder-functions @gol @@ -3888,6 +3889,88 @@ in the case of @code{scanf} formats, this option suppresses the warning if the unused arguments are all pointers, since the Single Unix Specification says that such unused arguments are allowed. +@item -Wformat-length +@itemx -Wformat-length=@var{level} +@opindex Wformat-length +@opindex Wno-format-length +Warn about calls to formatted input/output functions such as @code{sprintf} +that might overflow the destination buffer, or about bounded functions such +as @code{snprintf} that might result in output truncation. When the exact +number of bytes written by a format directive cannot be determined at +compile-time it is estimated based on heuristics that depend on the +@var{level} argument and on optimization. While enabling optimization +will in most cases improve the accuracy of the warning, it may also +result in false positives. + +@table @gcctabopt +@item -Wformat-length +@item -Wformat-length=1 +@opindex Wformat-length +@opindex Wno-format-length +Level @var{1} of @option{-Wformat-length} enabled by @option{-Wformat} +employs a conservative approach that warns only about calls that most +likely overflow the buffer or result in output truncation. At this +level, numeric arguments to format directives with unknown values are +assumed to have the value of one, and strings of unknown length to be +empty. Numeric arguments that are known to be bounded to a subrange +of their type, or string arguments whose output is bounded either by +their directive's precision or by a finite set of string literals, are +assumed to take on the value within the range that results in the most +bytes on output. For example, the call to @code{sprintf} below is +diagnosed because even with both @var{a} and @var{b} equal to zero, +the terminating NUL character (@code{'\0'}) appended by the function +to the destination buffer will be written past its end. Increasing +the size of the buffer by a single byte is sufficient to avoid the +warning, though it may not be sufficient to avoid the overflow. + +@smallexample +void f (int a, int b) +@{ + char buf [12]; + sprintf (buf, "a = %i, b = %i\n", a, b); +@} +@end smallexample + +@item -Wformat-length=2 +Level @var{2} warns also about calls that might overflow the destination +buffer or result in truncation given an argument of sufficient length +or magnitude. At level @var{2}, unknown numeric arguments are assumed +to have the minimum representable value for signed types with a precision +greater than 1, and the maximum representable value otherwise. Unknown +string arguments whose length cannot be assumed to be bounded either by +the directive's precision, or by a finite set of string literals they +may evaluate to, or the character array they may point to, are assumed +to be 1 character long. + +At level @var{2}, the call in the example above is again diagnosed, but +this time because with @var{a} equal to a 32-bit @code{INT_MIN} the first +@code{%i} directive will write some of its digits beyond the end of +the destination buffer. To make the call safe regardless of the values +of the two variables, the size of the destination buffer must be increased +to at least 34 bytes. GCC includes the minimum size of the buffer in +an informational note following the warning. + +An alternative to increasing the size of the destination buffer is to +constrain the range of formatted values. The maximum length of string +arguments can be bounded by specifying the precision in the format +directive. When numeric arguments of format directives can be assumed +to be bounded by less than the precision of their type, choosing +an appropriate length modifier to the format specifier will reduce +the required buffer size. For example, if @var{a} and @var{b} in the +example above can be assumed to be within the precision of +the @code{short int} type then using either the @code{%hi} format +directive or casting the argument to @code{short} reduces the maximum +required size of the buffer to 24 bytes. + +@smallexample +void f (int a, int b) +@{ + char buf [23]; + sprintf (buf, "a = %hi, b = %i\n", a, (short)b); +@} +@end smallexample +@end table + @item -Wno-format-zero-length @opindex Wno-format-zero-length @opindex Wformat-zero-length @@ -7855,6 +7938,30 @@ dependent on the structure of loops within the source code. Disabled at level @option{-Os}. +@item -fprintf-return-value +@opindex fprintf-return-value +Substitute constants for known return value of formatted output functions +such as @code{sprintf}, @code{snprintf}, @code{vsprintf}, and @code{vsnprintf} +(but not @code{printf} of @code{fprintf}). This transformation allows GCC +to optimize or even eliminate branches based on the known return value of +these functions called with arguments that are either constant, or whose +values are known to be in a range that makes determining the exact return +value possible. For example, both the branch and the body of the @code{if} +statement (but not the call to @code{snprint}) can be optimized away when +@code{i} is a 32-bit or smaller integer because the return value is guaranteed +to be at most 8. + +@smallexample +char buf[9]; +if (snprintf (buf, "%08x", i) >= sizeof buf) + @dots{} +@end smallexample + +The @option{-fprintf-return-value} option relies on other optimizations +and yields best results with @option{-O2}. It works in tandem with the +@option{-Wformat-length} option. The @option{-fprintf-return-value} +option is disabled by default. + @item -fno-peephole @itemx -fno-peephole2 @opindex fno-peephole diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi index 92554d68620..61d3a67c88e 100644 --- a/gcc/doc/tm.texi +++ b/gcc/doc/tm.texi @@ -5332,6 +5332,10 @@ In either case, it remains possible to select code-generation for the alternate scheme, by means of compiler command line switches. @end defmac +@deftypefn {Target Hook} {const char*} TARGET_PRINTF_POINTER_FORMAT (tree, const char **@var{flags}) +Determine the target @code{printf} implementation format string that the most closely corresponds to the @code{%p} format directive. The object pointed to by the @var{flags} is set to a string consisting of recognized format flags such as the @code{'#'} character. +@end deftypefn + @node Addressing Modes @section Addressing Modes @cindex addressing modes diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in index b1482381c9a..fd3d65dcf3e 100644 --- a/gcc/doc/tm.texi.in +++ b/gcc/doc/tm.texi.in @@ -4065,6 +4065,8 @@ In either case, it remains possible to select code-generation for the alternate scheme, by means of compiler command line switches. @end defmac +@hook TARGET_PRINTF_POINTER_FORMAT + @node Addressing Modes @section Addressing Modes @cindex addressing modes diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c index 2e0bd806987..addabb745ef 100644 --- a/gcc/gimple-fold.c +++ b/gcc/gimple-fold.c @@ -1159,21 +1159,30 @@ gimple_fold_builtin_memset (gimple_stmt_iterator *gsi, tree c, tree len) } -/* Return the string length, maximum string length or maximum value of - ARG in LENGTH. - If ARG is an SSA name variable, follow its use-def chains. If LENGTH - is not NULL and, for TYPE == 0, its value is not equal to the length - we determine or if we are unable to determine the length or value, - return false. VISITED is a bitmap of visited variables. - TYPE is 0 if string length should be returned, 1 for maximum string - length and 2 for maximum value ARG can have. */ +/* Obtain the minimum and maximum string length or minimum and maximum + value of ARG in LENGTH[0] and LENGTH[1], respectively. + If ARG is an SSA name variable, follow its use-def chains. When + TYPE == 0, if LENGTH[1] is not equal to the length we determine or + if we are unable to determine the length or value, return False. + VISITED is a bitmap of visited variables. + TYPE is 0 if string length should be obtained, 1 for maximum string + length and 2 for maximum value ARG can have. + When FUZZY is set and the length of a string cannot be determined, + the function instead considers as the maximum possible length the + size of a character array it may refer to. */ static bool -get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type) +get_range_strlen (tree arg, tree length[2], bitmap *visited, int type, + bool fuzzy) { tree var, val; gimple *def_stmt; + /* The minimum and maximum length. The MAXLEN pointer stays unchanged + but MINLEN may be cleared during the execution of the function. */ + tree *minlen = length; + tree *const maxlen = length + 1; + if (TREE_CODE (arg) != SSA_NAME) { /* We can end up with &(*iftmp_1)[0] here as well, so handle it. */ @@ -1184,8 +1193,8 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type) tree aop0 = TREE_OPERAND (TREE_OPERAND (arg, 0), 0); if (TREE_CODE (aop0) == INDIRECT_REF && TREE_CODE (TREE_OPERAND (aop0, 0)) == SSA_NAME) - return get_maxval_strlen (TREE_OPERAND (aop0, 0), - length, visited, type); + return get_range_strlen (TREE_OPERAND (aop0, 0), + length, visited, type, fuzzy); } if (type == 2) @@ -1197,26 +1206,60 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type) } else val = c_strlen (arg, 1); + + if (!val && fuzzy) + { + if (TREE_CODE (arg) == ADDR_EXPR) + return get_range_strlen (TREE_OPERAND (arg, 0), length, + visited, type, fuzzy); + + if (TREE_CODE (arg) == COMPONENT_REF + && TREE_CODE (TREE_TYPE (TREE_OPERAND (arg, 1))) == ARRAY_TYPE) + { + /* Use the type of the member array to determine the upper + bound on the length of the array. This may be overly + optimistic if the array itself isn't NUL-terminated and + the caller relies on the subsequent member to contain + the NUL. */ + arg = TREE_OPERAND (arg, 1); + val = TYPE_SIZE_UNIT (TREE_TYPE (arg)); + if (!val || integer_zerop (val)) + return false; + val = fold_build2 (MINUS_EXPR, TREE_TYPE (val), val, + integer_one_node); + /* Avoid using the array size as the minimum. */ + minlen = NULL; + } + } + if (!val) return false; - if (*length) + if (minlen + && (!*minlen + || (type > 0 + && TREE_CODE (*minlen) == INTEGER_CST + && TREE_CODE (val) == INTEGER_CST + && tree_int_cst_lt (val, *minlen)))) + *minlen = val; + + if (*maxlen) { if (type > 0) { - if (TREE_CODE (*length) != INTEGER_CST + if (TREE_CODE (*maxlen) != INTEGER_CST || TREE_CODE (val) != INTEGER_CST) return false; - if (tree_int_cst_lt (*length, val)) - *length = val; + if (tree_int_cst_lt (*maxlen, val)) + *maxlen = val; return true; } - else if (simple_cst_equal (val, *length) != 1) + else if (simple_cst_equal (val, *maxlen) != 1) return false; } - *length = val; + *maxlen = val; return true; } @@ -1244,14 +1287,14 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type) || gimple_assign_unary_nop_p (def_stmt)) { tree rhs = gimple_assign_rhs1 (def_stmt); - return get_maxval_strlen (rhs, length, visited, type); + return get_range_strlen (rhs, length, visited, type, fuzzy); } else if (gimple_assign_rhs_code (def_stmt) == COND_EXPR) { tree op2 = gimple_assign_rhs2 (def_stmt); tree op3 = gimple_assign_rhs3 (def_stmt); - return get_maxval_strlen (op2, length, visited, type) - && get_maxval_strlen (op3, length, visited, type); + return get_range_strlen (op2, length, visited, type, fuzzy) + && get_range_strlen (op3, length, visited, type, fuzzy); } return false; @@ -1274,8 +1317,13 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type) if (arg == gimple_phi_result (def_stmt)) continue; - if (!get_maxval_strlen (arg, length, visited, type)) - return false; + if (!get_range_strlen (arg, length, visited, type, fuzzy)) + { + if (fuzzy) + *maxlen = build_all_ones_cst (size_type_node); + else + return false; + } } } return true; @@ -1285,17 +1333,40 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type) } } +/* Determine the minimum and maximum value or string length that ARG + refers to and store each in the first two elements of MINMAXLEN. + For expressions that point to strings of unknown lengths that are + character arrays, use the upper bound of the array as the maximum + length. For example, given an expression like 'x ? array : "xyz"' + and array declared as 'char array[8]', MINMAXLEN[0] will be set + to 3 and MINMAXLEN[1] to 7, the longest string that could be + stored in array. +*/ + +void get_range_strlen (tree arg, tree minmaxlen[2]) +{ + bitmap visited = NULL; + + minmaxlen[0] = NULL_TREE; + minmaxlen[1] = NULL_TREE; + + get_range_strlen (arg, minmaxlen, &visited, 1, true); + + if (visited) + BITMAP_FREE (visited); +} + tree get_maxval_strlen (tree arg, int type) { bitmap visited = NULL; - tree len = NULL_TREE; - if (!get_maxval_strlen (arg, &len, &visited, type)) - len = NULL_TREE; + tree len[2] = { NULL_TREE, NULL_TREE }; + if (!get_range_strlen (arg, len, &visited, type, false)) + len[1] = NULL_TREE; if (visited) BITMAP_FREE (visited); - return len; + return len[1]; } diff --git a/gcc/gimple-fold.h b/gcc/gimple-fold.h index f3147144029..5add30c99bd 100644 --- a/gcc/gimple-fold.h +++ b/gcc/gimple-fold.h @@ -24,6 +24,8 @@ along with GCC; see the file COPYING3. If not see extern tree canonicalize_constructor_val (tree, tree); extern tree get_symbol_constant_value (tree); +extern void get_range_strlen (tree, tree[2]); +extern tree get_maxval_strlen (tree, int); extern void gimplify_and_update_call_from_tree (gimple_stmt_iterator *, tree); extern bool fold_stmt (gimple_stmt_iterator *); extern bool fold_stmt (gimple_stmt_iterator *, tree (*) (tree)); diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c new file mode 100644 index 00000000000..0afcf68c7bb --- /dev/null +++ b/gcc/gimple-ssa-sprintf.c @@ -0,0 +1,2686 @@ +/* Copyright (C) 2016 Free Software Foundation, Inc. + Contributed by Martin Sebor . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +/* This file implements the printf-return-value pass. The pass does + two things: 1) it analyzes calls to formatted output functions like + sprintf looking for possible buffer overflows and calls to bounded + functions like snprintf for early truncation (and under the control + of the -Wformat-length option issues warnings), and 2) under the + control of the -fprintf-return-value option it folds the return + value of safe calls into constants, making it possible to eliminate + code that depends on the value of those constants. + + For all functions (bounded or not) the pass uses the size of the + destination object. That means that it will diagnose calls to + snprintf not on the basis of the size specified by the function's + second argument but rathger on the basis of the size the first + argument points to (if possible). For bound-checking built-ins + like __builtin___snprintf_chk the pass uses the size typically + determined by __builtin_object_size and passed to the built-in + by the Glibc inline wrapper. + + The pass handles all forms standard sprintf format directives, + including character, integer, floating point, pointer, and strings, + with the standard C flags, widths, and precisions. For integers + and strings it computes the length of output itself. For floating + point it uses MPFR to fornmat known constants with up and down + rounding and uses the resulting range of output lengths. For + strings it uses the length of string literals and the sizes of + character arrays that a character pointer may point to as a bound + on the longest string. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "tree.h" +#include "gimple.h" +#include "tree-pass.h" +#include "ssa.h" +#include "gimple-fold.h" +#include "gimple-pretty-print.h" +#include "diagnostic-core.h" +#include "fold-const.h" +#include "gimple-iterator.h" +#include "tree-ssa.h" +#include "tree-object-size.h" +#include "params.h" +#include "tree-cfg.h" +#include "calls.h" +#include "cfgloop.h" +#include "intl.h" + +#include "builtins.h" +#include "stor-layout.h" + +#include "realmpfr.h" +#include "target.h" +#include "targhooks.h" + +#include "cpplib.h" +#include "input.h" +#include "toplev.h" +#include "substring-locations.h" +#include "diagnostic.h" + +namespace { + +const pass_data pass_data_sprintf_length = { + GIMPLE_PASS, // pass type + "printf-return-value", // pass name + OPTGROUP_NONE, // optinfo_flags + TV_NONE, // tv_id + PROP_cfg, // properties_required + 0, // properties_provided + 0, // properties_destroyed + 0, // properties_start + 0, // properties_finish +}; + +struct format_result; + +class pass_sprintf_length : public gimple_opt_pass +{ + bool fold_return_value; + +public: + pass_sprintf_length (gcc::context *ctxt) + : gimple_opt_pass (pass_data_sprintf_length, ctxt), + fold_return_value (false) + { } + + opt_pass * clone () { return new pass_sprintf_length (m_ctxt); } + + virtual bool gate (function *); + + virtual unsigned int execute (function *); + + void set_pass_param (unsigned int n, bool param) + { + gcc_assert (n == 0); + fold_return_value = param; + } + + void handle_gimple_call (gimple_stmt_iterator); + + struct call_info; + void compute_format_length (const call_info &, format_result *); +}; + +bool +pass_sprintf_length::gate (function *) +{ + /* Run the pass iff -Warn-format-length is specified and either + not optimizing and the pass is being invoked early, or when + optimizing and the pass is being invoked during optimization + (i.e., "late"). */ + return ((0 < warn_format_length || flag_printf_return_value) + && (0 < optimize) == fold_return_value); +} + +/* The result of a call to a formatted function. */ + +struct format_result +{ + /* Number of characters written by the formatted function, exact, + minimum and maximum when an exact number cannot be determined. + Setting the minimum to HOST_WIDE_INT_MAX disables all length + tracking for the remainder of the format string. + Setting either of the other two members to HOST_WIDE_INT_MAX + disables the exact or maximum length tracking, respectively, + but continues to track the maximum. */ + unsigned HOST_WIDE_INT number_chars; + unsigned HOST_WIDE_INT number_chars_min; + unsigned HOST_WIDE_INT number_chars_max; + + /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX + is the output of all directives determined to be bounded to some + subrange of their types or possible lengths, false otherwise. + Note that BOUNDED only implies that the length of a function's + output is known to be within some range, not that it's constant + and a candidate for folding. */ + bool bounded; + + /* True when the output of the formatted call is constant (and + thus a candidate for string constant folding). This is rare + and typically requires that the arguments of all directives + are also constant. Constant implies bounded. */ + bool constant; + + /* True if no individual directive resulted in more than 4095 bytes + of output (the total NUMBER_CHARS might be greater). */ + bool under4k; + + /* True when a floating point directive has been seen in the format + string. */ + bool floating; + + /* True when an intermediate result has caused a warning. Used to + avoid issuing duplicate warnings while finishing the processing + of a call. */ + bool warned; + + /* Preincrement the number of output characters by 1. */ + format_result& operator++ () + { + return *this += 1; + } + + /* Postincrement the number of output characters by 1. */ + format_result operator++ (int) + { + format_result prev (*this); + *this += 1; + return prev; + } + + /* Increment the number of output characters by N. */ + format_result& operator+= (unsigned HOST_WIDE_INT n) + { + gcc_assert (n < HOST_WIDE_INT_MAX); + + if (number_chars < HOST_WIDE_INT_MAX) + number_chars += n; + if (number_chars_min < HOST_WIDE_INT_MAX) + number_chars_min += n; + if (number_chars_max < HOST_WIDE_INT_MAX) + number_chars_max += n; + return *this; + } +}; + +/* Return the value of INT_MIN for the target. */ + +static HOST_WIDE_INT +target_int_min () +{ + static const unsigned HOST_WIDE_INT int_min + = 1LLU << (sizeof int_min * CHAR_BIT + - TYPE_PRECISION (integer_type_node) + 1); + return int_min; +} + +/* Return the value of INT_MAX for the target. */ + +static unsigned HOST_WIDE_INT +target_int_max () +{ + static const unsigned HOST_WIDE_INT int_max + = HOST_WIDE_INT_M1U >> (sizeof int_max * CHAR_BIT + - TYPE_PRECISION (integer_type_node) + 1); + return int_max; +} + +/* Return the constant initial value of DECL if available or DECL + otherwise. Same as the synonymous function in c/c-typeck.c. */ + +static tree +decl_constant_value (tree decl) +{ + if (/* Don't change a variable array bound or initial value to a constant + in a place where a variable is invalid. Note that DECL_INITIAL + isn't valid for a PARM_DECL. */ + current_function_decl != 0 + && TREE_CODE (decl) != PARM_DECL + && !TREE_THIS_VOLATILE (decl) + && TREE_READONLY (decl) + && DECL_INITIAL (decl) != 0 + && TREE_CODE (DECL_INITIAL (decl)) != ERROR_MARK + /* This is invalid if initial value is not constant. + If it has either a function call, a memory reference, + or a variable, then re-evaluating it could give different results. */ + && TREE_CONSTANT (DECL_INITIAL (decl)) + /* Check for cases where this is sub-optimal, even though valid. */ + && TREE_CODE (DECL_INITIAL (decl)) != CONSTRUCTOR) + return DECL_INITIAL (decl); + return decl; +} + +/* Given FORMAT, set *PLOC to the source location of the format string + and return the format string if it is known or null otherwise. */ + +static const char* +get_format_string (tree format, location_t *ploc) +{ + if (VAR_P (format)) + { + /* Pull out a constant value if the front end didn't. */ + format = decl_constant_value (format); + STRIP_NOPS (format); + } + + if (integer_zerop (format)) + { + /* FIXME: Diagnose null format string if it hasn't been diagnosed + by -Wformat (the latter diagnoses only nul pointer constants, + this pass can do better). */ + return NULL; + } + + HOST_WIDE_INT offset = 0; + + if (TREE_CODE (format) == POINTER_PLUS_EXPR) + { + tree arg0 = TREE_OPERAND (format, 0); + tree arg1 = TREE_OPERAND (format, 1); + STRIP_NOPS (arg0); + STRIP_NOPS (arg1); + + if (TREE_CODE (arg1) != INTEGER_CST) + return NULL; + + format = arg0; + + /* POINTER_PLUS_EXPR offsets are to be interpreted signed. */ + if (!cst_and_fits_in_hwi (arg1)) + return NULL; + + offset = int_cst_value (arg1); + } + + if (TREE_CODE (format) != ADDR_EXPR) + return NULL; + + *ploc = EXPR_LOC_OR_LOC (format, input_location); + + format = TREE_OPERAND (format, 0); + + if (TREE_CODE (format) == ARRAY_REF + && tree_fits_shwi_p (TREE_OPERAND (format, 1)) + && (offset += tree_to_shwi (TREE_OPERAND (format, 1))) >= 0) + format = TREE_OPERAND (format, 0); + + if (offset < 0) + return NULL; + + tree array_init; + tree array_size = NULL_TREE; + + if (VAR_P (format) + && TREE_CODE (TREE_TYPE (format)) == ARRAY_TYPE + && (array_init = decl_constant_value (format)) != format + && TREE_CODE (array_init) == STRING_CST) + { + /* Extract the string constant initializer. Note that this may + include a trailing NUL character that is not in the array (e.g. + const char a[3] = "foo";). */ + array_size = DECL_SIZE_UNIT (format); + format = array_init; + } + + if (TREE_CODE (format) != STRING_CST) + return NULL; + + if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format))) != char_type_node) + { + /* Wide format string. */ + return NULL; + } + + const char *fmtstr = TREE_STRING_POINTER (format); + unsigned fmtlen = TREE_STRING_LENGTH (format); + + if (array_size) + { + /* Variable length arrays can't be initialized. */ + gcc_assert (TREE_CODE (array_size) == INTEGER_CST); + + if (tree_fits_shwi_p (array_size)) + { + HOST_WIDE_INT array_size_value = tree_to_shwi (array_size); + if (array_size_value > 0 + && array_size_value == (int) array_size_value + && fmtlen > array_size_value) + fmtlen = array_size_value; + } + } + if (offset) + { + if (offset >= fmtlen) + return NULL; + + fmtstr += offset; + fmtlen -= offset; + } + + if (fmtlen < 1 || fmtstr[--fmtlen] != 0) + { + /* FIXME: Diagnose an unterminated format string if it hasn't been + diagnosed by -Wformat. Similarly to a null format pointer, + -Wformay diagnoses only nul pointer constants, this pass can + do better). */ + return NULL; + } + + return fmtstr; +} + +/* The format_warning_at_substring function is not used here in a way + that makes using attribute format viable. Suppress the warning. */ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=format" + +/* For convenience and brevity. */ + +static bool + (* const fmtwarn) (const substring_loc &, const source_range *, + const char *, int, const char *, ...) + = format_warning_at_substring; + +/* Format length modifiers. */ + +enum format_lengths +{ + FMT_LEN_none, + FMT_LEN_hh, // char argument + FMT_LEN_h, // short + FMT_LEN_l, // long + FMT_LEN_ll, // long long + FMT_LEN_L, // long double (and GNU long long) + FMT_LEN_z, // size_t + FMT_LEN_t, // ptrdiff_t + FMT_LEN_j // intmax_t +}; + + +/* A minimum and maximum number of bytes. */ + +struct result_range +{ + unsigned HOST_WIDE_INT min, max; +}; + +/* Description of the result of conversion either of a single directive + or the whole format string. */ + +struct fmtresult +{ + /* The range a directive's argument is in. */ + tree argmin, argmax; + + /* The minimum and maximum number of bytes that a directive + results in on output for an argument in the range above. */ + result_range range; + + /* True when the range is the result of an argument determined + to be bounded to a subrange of its type or value (such as by + value range propagation or the width of the formt directive), + false otherwise. */ + bool bounded; + /* True when the output of a directive is constant. This is rare + and typically requires that the argument(s) of the directive + are also constant (such as determined by constant propagation, + though not value range propagation). */ + bool constant; +}; + +/* Description of a conversion specification. */ + +struct conversion_spec +{ + /* A bitmap of flags, one for each character. */ + unsigned flags[256 / sizeof (int)]; + /* Numeric width as in "%8x". */ + int width; + /* Numeric precision as in "%.32s". */ + int precision; + + /* Width specified via the '*' character. */ + tree star_width; + /* Precision specified via the asterisk. */ + tree star_precision; + + /* Length modifier. */ + format_lengths modifier; + + /* Format specifier character. */ + char specifier; + + /* Numeric width was given. */ + unsigned have_width: 1; + /* Numeric precision was given. */ + unsigned have_precision: 1; + /* Non-zero when certain flags should be interpreted even for a directive + that normally doesn't accept them (used when "%p" with flags such as + space or plus is interepreted as a "%x". */ + unsigned force_flags: 1; + + /* Format conversion function that given a conversion specification + and an argument returns the formatting result. */ + fmtresult (*fmtfunc) (const conversion_spec &, tree); + + /* Return True when a the format flag CHR has been used. */ + bool get_flag (char chr) const + { + unsigned char c = chr & 0xff; + return (flags[c / (CHAR_BIT * sizeof *flags)] + & (1U << (c % (CHAR_BIT * sizeof *flags)))); + } + + /* Make a record of the format flag CHR having been used. */ + void set_flag (char chr) + { + unsigned char c = chr & 0xff; + flags[c / (CHAR_BIT * sizeof *flags)] + |= (1U << (c % (CHAR_BIT * sizeof *flags))); + } + + /* Reset the format flag CHR. */ + void clear_flag (char chr) + { + unsigned char c = chr & 0xff; + flags[c / (CHAR_BIT * sizeof *flags)] + &= ~(1U << (c % (CHAR_BIT * sizeof *flags))); + } +}; + +/* Return the logarithm of X in BASE. */ + +static int +ilog (unsigned HOST_WIDE_INT x, int base) +{ + int res = 0; + do + { + ++res; + x /= base; + } while (x); + return res; +} + +/* Return the number of bytes resulting from converting into a string + the INTEGER_CST tree node X in BASE. PLUS indicates whether 1 for + a plus sign should be added for positive numbers, and PREFIX whether + the length of an octal ('O') or hexadecimal ('0x') prefix should be + added for nonzero numbers. Return -1 if X cannot be represented. */ + +static int +tree_digits (tree x, int base, bool plus, bool prefix) +{ + unsigned HOST_WIDE_INT absval; + + int res; + + if (TYPE_UNSIGNED (TREE_TYPE (x))) + { + if (tree_fits_uhwi_p (x)) + { + absval = tree_to_uhwi (x); + res = plus; + } + else + return -1; + } + else + { + if (tree_fits_shwi_p (x)) + { + HOST_WIDE_INT i = tree_to_shwi (x); + if (i < 0) + { + absval = -i; + res = 1; + } + else + { + absval = i; + res = plus; + } + } + else + return -1; + } + + res += ilog (absval, base); + + if (prefix && absval) + { + if (base == 8) + res += 1; + else if (base == 16) + res += 2; + } + + return res; +} + +/* Given the formatting result described by RES and NAVAIL, the number + of available in the destination, return the number of bytes remaining + in the destination. */ + +static inline result_range +bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res) +{ + result_range range; + + if (HOST_WIDE_INT_MAX <= navail) + { + range.min = range.max = navail; + return range; + } + + if (res.number_chars < navail) + { + range.min = range.max = navail - res.number_chars; + } + else if (res.number_chars_min < navail) + { + range.max = navail - res.number_chars_min; + } + else + range.max = 0; + + if (res.number_chars_max < navail) + range.min = navail - res.number_chars_max; + else + range.min = 0; + + return range; +} + +/* Given the formatting result described by RES and NAVAIL, the number + of available in the destination, return the minimum number of bytes + remaining in the destination. */ + +static inline unsigned HOST_WIDE_INT +min_bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res) +{ + if (HOST_WIDE_INT_MAX <= navail) + return navail; + + if (1 < warn_format_length || res.bounded) + { + /* At level 2, or when all directives output an exact number + of bytes or when their arguments were bounded by known + ranges, use the greater of the two byte counters if it's + valid to compute the result. */ + if (res.number_chars_max < HOST_WIDE_INT_MAX) + navail -= res.number_chars_max; + else if (res.number_chars < HOST_WIDE_INT_MAX) + navail -= res.number_chars; + else if (res.number_chars_min < HOST_WIDE_INT_MAX) + navail -= res.number_chars_min; + } + else + { + /* At level 1 use the smaller of the byte counters to compute + the result. */ + if (res.number_chars < HOST_WIDE_INT_MAX) + navail -= res.number_chars; + else if (res.number_chars_min < HOST_WIDE_INT_MAX) + navail -= res.number_chars_min; + else if (res.number_chars_max < HOST_WIDE_INT_MAX) + navail -= res.number_chars_max; + } + + if (navail > HOST_WIDE_INT_MAX) + navail = 0; + + return navail; +} + +/* Description of a call to a formatted function. */ + +struct pass_sprintf_length::call_info +{ + /* Function call statement. */ + gimple *callstmt; + + /* Function called. */ + tree func; + + /* Called built-in function code. */ + built_in_function fncode; + + /* Format argument and format string extracted from it. */ + tree format; + const char *fmtstr; + + /* The location of the format argument. */ + location_t fmtloc; + + /* The destination object size for __builtin___xxx_chk functions + typically determined by __builtin_object_size, or -1 if unknown. */ + unsigned HOST_WIDE_INT objsize; + + /* Number of the first variable argument. */ + unsigned HOST_WIDE_INT argidx; + + /* True for functions like snprintf that specify the size of + the destination, false for others like sprintf that don't. */ + bool bounded; +}; + +/* Return the result of formatting the '%%' directive. */ + +static fmtresult +format_percent (const conversion_spec &, tree) +{ + fmtresult res; + res.argmin = res.argmax = NULL_TREE; + res.range.min = res.range.max = 1; + res.bounded = res.constant = true; + return res; +} + + +/* Ugh. Compute intmax_type_node and uintmax_type_node the same way + lto/lto-lang.c does it. This should be available in tree.h. */ + +static void +build_intmax_type_nodes (tree *pintmax, tree *puintmax) +{ + if (strcmp (SIZE_TYPE, "unsigned int") == 0) + { + *pintmax = integer_type_node; + *puintmax = unsigned_type_node; + } + else if (strcmp (SIZE_TYPE, "long unsigned int") == 0) + { + *pintmax = long_integer_type_node; + *puintmax = long_unsigned_type_node; + } + else if (strcmp (SIZE_TYPE, "long long unsigned int") == 0) + { + *pintmax = long_long_integer_type_node; + *puintmax = long_long_unsigned_type_node; + } + else + { + for (int i = 0; i < NUM_INT_N_ENTS; i++) + if (int_n_enabled_p[i]) + { + char name[50]; + sprintf (name, "__int%d unsigned", int_n_data[i].bitsize); + + if (strcmp (name, SIZE_TYPE) == 0) + { + *pintmax = int_n_trees[i].signed_type; + *puintmax = int_n_trees[i].unsigned_type; + } + } + } +} + +static fmtresult +format_integer (const conversion_spec &, tree); + +/* Return a range representing the minimum and maximum number of bytes + that the conversion specification SPEC will write on output for the + pointer argument ARG when non-null. ARG may be null (for vararg + functions). */ + +static fmtresult +format_pointer (const conversion_spec &spec, tree arg) +{ + fmtresult res = fmtresult (); + + /* Determine the target's integer format corresponding to "%p". */ + const char *flags; + const char *pfmt = targetm.printf_pointer_format (arg, &flags); + if (!pfmt) + { + /* The format couldn't be determined. */ + res.range.min = res.range.max = HOST_WIDE_INT_M1U; + return res; + } + + if (pfmt [0] == '%') + { + /* Format the pointer using the integer format string. */ + conversion_spec pspec = spec; + + /* Clear flags that are not listed as recognized. */ + for (const char *pf = "+ #0"; *pf; ++pf) + { + if (!strchr (flags, *pf)) + pspec.clear_flag (*pf); + } + + /* Set flags that are specified in the format string. */ + bool flag_p = true; + do + { + switch (*++pfmt) + { + case '+': case ' ': case '#': case '0': + pspec.set_flag (*pfmt); + break; + default: + flag_p = false; + } + } + while (flag_p); + + /* Set the appropriate length modifier taking care to clear + the one that may be set (Glibc's %p accepts but ignores all + the integer length modifiers). */ + switch (*pfmt) + { + case 'l': pspec.modifier = FMT_LEN_l; ++pfmt; break; + case 't': pspec.modifier = FMT_LEN_t; ++pfmt; break; + case 'z': pspec.modifier = FMT_LEN_z; ++pfmt; break; + default: pspec.modifier = FMT_LEN_none; + } + + pspec.force_flags = 1; + pspec.specifier = *pfmt++; + gcc_assert (*pfmt == '\0'); + return format_integer (pspec, arg); + } + + /* The format is a plain string such as Glibc's "(nil)". */ + res.range.min = res.range.max = strlen (pfmt); + return res; +} + +/* Return a range representing the minimum and maximum number of bytes + that the conversion specification SPEC will write on output for the + integer argument ARG when non-null. ARG may be null (for vararg + functions). */ + +static fmtresult +format_integer (const conversion_spec &spec, tree arg) +{ + /* These are available as macros in the C and C++ front ends but, + sadly, not here. */ + static tree intmax_type_node; + static tree uintmax_type_node; + + /* Initialize the intmax nodes above the first time through here. */ + if (!intmax_type_node) + build_intmax_type_nodes (&intmax_type_node, &uintmax_type_node); + + /* Set WIDTH and PRECISION to either the values in the format + specification or to zero. */ + int width = spec.have_width ? spec.width : 0; + int prec = spec.have_precision ? spec.precision : 0; + + if (spec.star_width) + width = (TREE_CODE (spec.star_width) == INTEGER_CST + ? tree_to_shwi (spec.star_width) : 0); + + if (spec.star_precision) + prec = (TREE_CODE (spec.star_precision) == INTEGER_CST + ? tree_to_shwi (spec.star_precision) : 0); + + bool sign = spec.specifier == 'd' || spec.specifier == 'i'; + + /* The type of the "formal" argument expected by the directive. */ + tree dirtype = NULL_TREE; + + /* Determine the expected type of the argument from the length + modifier. */ + switch (spec.modifier) + { + case FMT_LEN_none: + if (spec.specifier == 'p') + dirtype = ptr_type_node; + else + dirtype = sign ? integer_type_node : unsigned_type_node; + break; + + case FMT_LEN_h: + dirtype = sign ? short_integer_type_node : short_unsigned_type_node; + break; + + case FMT_LEN_hh: + dirtype = sign ? signed_char_type_node : unsigned_char_type_node; + break; + + case FMT_LEN_l: + dirtype = sign ? long_integer_type_node : long_unsigned_type_node; + break; + + case FMT_LEN_L: + case FMT_LEN_ll: + dirtype = sign ? long_integer_type_node : long_unsigned_type_node; + break; + + case FMT_LEN_z: + dirtype = sign ? ptrdiff_type_node : size_type_node; + break; + + case FMT_LEN_t: + dirtype = sign ? ptrdiff_type_node : size_type_node; + break; + + case FMT_LEN_j: + dirtype = sign ? intmax_type_node : uintmax_type_node; + break; + + default: + gcc_unreachable (); + } + + /* The type of the argument to the directive, either deduced from + the actual non-constant argument if one is known, or from + the directive itself when none has been provided because it's + a va_list. */ + tree argtype = NULL_TREE; + + if (!arg) + { + /* When the argument has not been provided, use the type of + the directive's argument as an approximation. This will + result in false positives for directives like %i with + arguments with smaller precision (such as short or char). */ + argtype = dirtype; + } + else if (TREE_CODE (arg) == INTEGER_CST) + { + /* The minimum and maximum number of bytes produced by + the directive. */ + fmtresult res = fmtresult (); + + /* When a constant argument has been provided use its value + rather than type to determine the length of the output. */ + res.bounded = true; + res.constant = true; + + /* Base to format the number in. */ + int base; + + /* True when a signed conversion is preceded by a sign or space. */ + bool maybesign; + + switch (spec.specifier) + { + case 'd': + case 'i': + /* Space is only effective for signed conversions. */ + maybesign = spec.get_flag (' '); + base = 10; + break; + case 'u': + maybesign = spec.force_flags ? spec.get_flag (' ') : false; + base = 10; + break; + case 'o': + maybesign = spec.force_flags ? spec.get_flag (' ') : false; + base = 8; + break; + case 'X': + case 'x': + maybesign = spec.force_flags ? spec.get_flag (' ') : false; + base = 16; + break; + default: + gcc_unreachable (); + } + + /* Convert the argument to the type of the directive. */ + arg = fold_convert (dirtype, arg); + + maybesign |= spec.get_flag ('+'); + + /* True when a conversion is preceded by a prefix indicating the base + of the argument (octal or hexadecimal). */ + bool maybebase = spec.get_flag ('#'); + int len = tree_digits (arg, base, maybesign, maybebase); + + if (len < prec) + len = prec; + + if (len < width) + len = width; + + res.range.max = len; + res.range.min = res.range.max; + res.bounded = true; + + return res; + } + else if (TREE_CODE (TREE_TYPE (arg)) == INTEGER_TYPE + || TREE_CODE (TREE_TYPE (arg)) == POINTER_TYPE) + { + /* Determine the type of the provided non-constant argument. */ + if (TREE_CODE (arg) == NOP_EXPR) + arg = TREE_OPERAND (arg, 0); + else if (TREE_CODE (arg) == CONVERT_EXPR) + arg = TREE_OPERAND (arg, 0); + if (TREE_CODE (arg) == COMPONENT_REF) + arg = TREE_OPERAND (arg, 1); + + argtype = TREE_TYPE (arg); + } + else + { + /* Don't bother with invalid arguments since they likely would + have already been diagnosed, and disable any further checking + of the format string by returning [-1, -1]. */ + fmtresult res = fmtresult (); + res.range.min = res.range.max = HOST_WIDE_INT_M1U; + return res; + } + + fmtresult res = fmtresult (); + + /* Using either the range the non-constant argument is in, or its + type (either "formal" or actual), create a range of values that + constrain the length of output given the warning level. */ + tree argmin = NULL_TREE; + tree argmax = NULL_TREE; + + if (arg && TREE_CODE (arg) == SSA_NAME + && TREE_CODE (argtype) == INTEGER_TYPE) + { + /* Try to determine the range of values of the integer argument + (range information is not available for pointers). */ + wide_int min, max; + enum value_range_type range_type = get_range_info (arg, &min, &max); + if (range_type == VR_RANGE) + { + res.argmin = build_int_cst (argtype, wi::fits_uhwi_p (min) + ? min.to_uhwi () : min.to_shwi ()); + res.argmax = build_int_cst (argtype, wi::fits_uhwi_p (max) + ? max.to_uhwi () : max.to_shwi ()); + + /* For a range with a negative lower bound and a non-negative + upper bound, use one to determine the minimum number of bytes + on output and whichever of the two bounds that results in + the greater number of bytes on output for the upper bound. + For example, for ARG in the range of [-3, 123], use 123 as + the upper bound for %i but -3 for %u. */ + if (wi::neg_p (min) && !wi::neg_p (max)) + { + argmin = build_int_cst (argtype, wi::fits_uhwi_p (min) + ? min.to_uhwi () : min.to_shwi ()); + + argmax = build_int_cst (argtype, wi::fits_uhwi_p (max) + ? max.to_uhwi () : max.to_shwi ()); + + int minbytes = format_integer (spec, res.argmin).range.min; + int maxbytes = format_integer (spec, res.argmax).range.max; + if (maxbytes < minbytes) + argmax = res.argmin; + + argmin = integer_zero_node; + } + else + { + argmin = res.argmin; + argmax = res.argmax; + } + + /* The argument is bounded by the range of values determined + by Value Range Propagation. */ + res.bounded = true; + } + else if (range_type == VR_ANTI_RANGE) + { + /* Handle anti-ranges if/when bug 71690 is resolved. */ + } + else if (range_type == VR_VARYING) + { + /* The argument here may be the result of promoting the actual + argument to int. Try to determine the type of the actual + argument before promotion and narrow down its range that + way. */ + gimple *def = SSA_NAME_DEF_STMT (arg); + if (gimple_code (def) == GIMPLE_ASSIGN) + { + tree_code code = gimple_assign_rhs_code (def); + if (code == NOP_EXPR) + argtype = TREE_TYPE (gimple_assign_rhs1 (def)); + } + } + } + + if (!argmin) + { + /* For an unknown argument (e.g., one passed to a vararg function) + or one whose value range cannot be determined, create a T_MIN + constant if the argument's type is signed and T_MAX otherwise, + and use those to compute the range of bytes that the directive + can output. */ + argmin = build_int_cst (argtype, 1); + + int typeprec = TYPE_PRECISION (dirtype); + int argprec = TYPE_PRECISION (argtype); + + if (argprec < typeprec || POINTER_TYPE_P (argtype)) + { + if (TYPE_UNSIGNED (argtype)) + argmax = build_all_ones_cst (argtype); + else + argmax = fold_build2 (LSHIFT_EXPR, argtype, integer_one_node, + build_int_cst (integer_type_node, + argprec - 1)); + } + else + { + argmax = fold_build2 (LSHIFT_EXPR, dirtype, integer_one_node, + build_int_cst (integer_type_node, + typeprec - 1)); + } + res.argmin = argmin; + res.argmax = argmax; + } + + /* Recursively compute the minimum and maximum from the known range, + taking care to swap them if the lower bound results in longer + output than the upper bound (e.g., in the range [-1, 0]. */ + res.range.min = format_integer (spec, argmin).range.min; + res.range.max = format_integer (spec, argmax).range.max; + + /* The result is bounded either when the argument is determined to be + (e.g., when it's within some range) or when the minimum and maximum + are the same. That can happen here for example when the specified + width is as wide as the greater of MIN and MAX, as would be the case + with sprintf (d, "%08x", x) with a 32-bit integer x. */ + res.bounded |= res.range.min == res.range.max; + + if (res.range.max < res.range.min) + { + unsigned HOST_WIDE_INT tmp = res.range.max; + res.range.max = res.range.min; + res.range.min = tmp; + } + + return res; +} + +/* Return the number of bytes to format using the format specifier + SPEC the largest value in the real floating TYPE. */ + +static int +format_floating_max (tree type, char spec) +{ + machine_mode mode = TYPE_MODE (type); + + /* IBM Extended mode. */ + if (MODE_COMPOSITE_P (mode)) + mode = DFmode; + + /* Get the real type format desription for the target. */ + const real_format *rfmt = REAL_MODE_FORMAT (mode); + REAL_VALUE_TYPE rv; + + { + char buf[256]; + get_max_float (rfmt, buf, sizeof buf); + real_from_string (&rv, buf); + } + + /* Convert the GCC real value representation with the precision + of the real type to the mpfr_t format with the GCC default + round-to-nearest mode. */ + mpfr_t x; + mpfr_init2 (x, rfmt->p); + mpfr_from_real (x, &rv, MPFR_RNDN); + + const char fmt[] = { '%', 'R', spec, '\0' }; + int n = mpfr_snprintf (NULL, 0, fmt, x); + return n; +} + +/* Return a range representing the minimum and maximum number of bytes + that the conversion specification SPEC will output for any argument + given the WIDTH and PRECISION (extracted from SPEC). This function + is used when the directive argument or its value isn't known. */ + +static fmtresult +format_floating (const conversion_spec &spec, int width, int prec) +{ + tree type; + bool ldbl = false; + + switch (spec.modifier) + { + case FMT_LEN_none: + type = double_type_node; + break; + + case FMT_LEN_L: + type = long_double_type_node; + ldbl = true; + break; + + case FMT_LEN_ll: + type = long_double_type_node; + ldbl = true; + break; + + default: + gcc_unreachable (); + } + + /* The minimum and maximum number of bytes produced by the directive. */ + fmtresult res = fmtresult (); + res.constant = false; + + /* Log10 of of the maximum number of exponent digits for the type. */ + int logexpdigs = 2; + + if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2) + { + /* The base in which the exponent is represented should always + be 2 in GCC. */ + + const double log10_2 = .30102999566398119521; + + /* Compute T_MAX_EXP for base 2. */ + int expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2; + logexpdigs = ilog (expdigs, 10); + } + + switch (spec.specifier) + { + case 'A': + case 'a': + { + /* The minimum output is "0x.p+0". */ + res.range.min = 6 + (0 < prec ? prec : 0); + + /* Compute the maximum just once. */ + static const int a_max[] = { + format_floating_max (double_type_node, 'a'), + format_floating_max (long_double_type_node, 'a') + }; + res.range.max = a_max [ldbl]; + break; + } + + case 'E': + case 'e': + { + bool sign = spec.get_flag ('+') || spec.get_flag (' '); + /* The minimum output is "[-+]1.234567e+00" regardless + of the value of the actual argument. */ + res.range.min = (sign + + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0) + + 2 /* e+ */ + 2); + /* The maximum output is the minimum plus sign (unless already + included), plus the difference between the minimum exponent + of 2 and the maximum exponent for the type. */ + res.range.max = res.range.min + !sign + logexpdigs - 2; + break; + } + + case 'F': + case 'f': + { + /* The minimum output is "1.234567" regardless of the value + of the actual argument. */ + res.range.min = 2 + (prec < 0 ? 6 : prec); + + /* Compute the maximum just once. */ + static const int f_max[] = { + format_floating_max (double_type_node, 'f'), + format_floating_max (long_double_type_node, 'f') + }; + res.range.max = f_max [ldbl]; + break; + } + case 'G': + case 'g': + { + /* The minimum is the same as for '%F'. */ + res.range.min = 2 + (prec < 0 ? 6 : prec); + + /* Compute the maximum just once. */ + static const int g_max[] = { + format_floating_max (double_type_node, 'g'), + format_floating_max (long_double_type_node, 'g') + }; + res.range.max = g_max [ldbl]; + break; + } + + default: + gcc_unreachable (); + } + + if (0 < width) + { + if (res.range.min < (unsigned)width) + res.range.min = width; + if (res.range.max < (unsigned)width) + res.range.max = width; + } + + /* The argument is only considered bounded when the range of output + bytes is exact. */ + res.bounded = res.range.min == res.range.max; + return res; +} + +/* Return a range representing the minimum and maximum number of bytes + that the conversion specification SPEC will write on output for the + floating argument ARG. */ + +static fmtresult +format_floating (const conversion_spec &spec, tree arg) +{ + int width = -1; + int prec = -1; + + /* The minimum and maximum number of bytes produced by the directive. */ + fmtresult res = fmtresult (); + res.constant = arg && TREE_CODE (arg) == REAL_CST; + + if (spec.have_width) + width = spec.width; + else if (spec.star_width) + { + if (TREE_CODE (spec.star_width) == INTEGER_CST) + width = tree_to_shwi (spec.star_width); + else + { + res.range.min = res.range.max = HOST_WIDE_INT_M1U; + return res; + } + } + + if (spec.have_precision) + prec = spec.precision; + else if (spec.star_precision) + { + if (TREE_CODE (spec.star_precision) == INTEGER_CST) + prec = tree_to_shwi (spec.star_precision); + else + { + res.range.min = res.range.max = HOST_WIDE_INT_M1U; + return res; + } + } + else if (res.constant && TOUPPER (spec.specifier) != 'A') + { + /* Specify the precision explicitly since mpfr_sprintf defaults + to zero. */ + prec = 6; + } + + if (res.constant) + { + /* Set up an array to easily iterate over. */ + unsigned HOST_WIDE_INT* const minmax[] = { + &res.range.min, &res.range.max + }; + + /* Get the real type format desription for the target. */ + const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg); + const real_format *rfmt = REAL_MODE_FORMAT (TYPE_MODE (TREE_TYPE (arg))); + + /* Convert the GCC real value representation with the precision + of the real type to the mpfr_t format with the GCC default + round-to-nearest mode. */ + mpfr_t mpfrval; + mpfr_init2 (mpfrval, rfmt->p); + mpfr_from_real (mpfrval, rvp, MPFR_RNDN); + + char fmtstr [40]; + char *pfmt = fmtstr; + *pfmt++ = '%'; + + /* Append flags. */ + for (const char *pf = "-+ #0"; *pf; ++pf) + if (spec.get_flag (*pf)) + *pfmt++ = *pf; + + /* Append width when specified and precision. */ + if (width != -1) + pfmt += sprintf (pfmt, "%i", width); + if (prec != -1) + pfmt += sprintf (pfmt, ".%i", prec); + + /* Append the MPFR 'R' floating type specifier (no length modifier + is necessary or allowed by MPFR for mpfr_t values). */ + *pfmt++ = 'R'; + + /* Save the position of the MPFR rounding specifier and skip over + it. It will be set in each iteration in the loop below. */ + char* const rndspec = pfmt++; + + /* Append the C type specifier and nul-terminate. */ + *pfmt++ = spec.specifier; + *pfmt = '\0'; + + for (int i = 0; i != sizeof minmax / sizeof *minmax; ++i) + { + /* Use the MPFR rounding specifier to round down in the first + iteration and then up. In most but not all cases this will + result in the same number of bytes. */ + *rndspec = "DU"[i]; + + /* Format it and store the result in the corresponding + member of the result struct. */ + *minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval); + } + + res.bounded = res.range.min < HOST_WIDE_INT_MAX; + return res; + } + + return format_floating (spec, width, prec); +} + +/* Return a FMTRESULT struct set to the lengths of the shortest and longest + strings referenced by the expression STR, or (-1, -1) when not known. + Used by the format_string function below. */ + +static fmtresult +get_string_length (tree str) +{ + if (!str) + { + fmtresult res; + res.range.min = HOST_WIDE_INT_MAX; + res.range.max = HOST_WIDE_INT_MAX; + res.bounded = false; + res.constant = false; + return res; + } + + if (tree slen = c_strlen (str, 1)) + { + /* Simply return the length of the string. */ + fmtresult res; + res.range.min = res.range.max = tree_to_shwi (slen); + res.bounded = true; + res.constant = true; + return res; + } + + /* Determine the length of the shortest and longest string referenced + by STR. Strings of unknown lengths are bounded by the sizes of + arrays that subexpressions of STR may refer to. Pointers that + aren't known to point any such arrays result in LENRANGE[1] set + to SIZE_MAX. */ + tree lenrange[2]; + get_range_strlen (str, lenrange); + + if (lenrange [0] || lenrange [1]) + { + fmtresult res = fmtresult (); + + res.range.min = (tree_fits_uhwi_p (lenrange[0]) + ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length); + res.range.max = (tree_fits_uhwi_p (lenrange[1]) + ? tree_to_uhwi (lenrange[1]) : HOST_WIDE_INT_M1U); + + /* Set RES.BOUNDED to true if and only if all strings referenced + by STR are known to be bounded (though not necessarily by their + actual length but perhaps by their maximum possible length). */ + res.bounded = res.range.max < HOST_WIDE_INT_MAX; + + /* Set RES.CONSTANT to false even though that may be overly + conservative in rare cases like: 'x ? a : b' where a and + b have the same lengths and consist of the same characters. */ + res.constant = false; + return res; + } + + return get_string_length (NULL_TREE); +} + +/* Return the minimum and maximum number of characters formatted + by the '%c' and '%s' format directives and ther wide character + forms for the argument ARG. ARG can be null (for functions + such as vsprinf). */ + +static fmtresult +format_string (const conversion_spec &spec, tree arg) +{ + unsigned width = spec.have_width && 0 < spec.width ? spec.width : 0; + int prec = spec.have_precision ? spec.precision : -1; + + if (spec.star_width) + { + width = (TREE_CODE (spec.star_width) == INTEGER_CST + ? tree_to_shwi (spec.star_width) : 0); + if (width > INT_MAX) + width = 0; + } + + if (spec.star_precision) + prec = (TREE_CODE (spec.star_precision) == INTEGER_CST + ? tree_to_shwi (spec.star_precision) : -1); + + fmtresult res = fmtresult (); + + /* The maximum number of bytes for an unknown wide character argument + to a "%lc" directive adjusted for precision but not field width. */ + const unsigned HOST_WIDE_INT max_bytes_for_unknown_wc + = (1 == warn_format_length ? 0 <= prec ? prec : 0 + : 2 == warn_format_length ? 0 <= prec ? prec : 1 + : 0 <= prec ? prec : 6 /* Longest UTF-8 sequence. */); + + /* The maximum number of bytes for an unknown string argument to either + a "%s" or "%ls" directive adjusted for precision but not field width. */ + const unsigned HOST_WIDE_INT max_bytes_for_unknown_str + = (1 == warn_format_length ? 0 <= prec ? prec : 0 + : 2 == warn_format_length ? 0 <= prec ? prec : 1 + : HOST_WIDE_INT_MAX); + + if (spec.specifier == 'c') + { + if (spec.modifier == FMT_LEN_l) + { + /* Positive if the argument is a wide NUL character? */ + int nul = (arg && TREE_CODE (arg) == INTEGER_CST + ? integer_zerop (arg) : -1); + + /* A '%lc' directive is the same as '%ls' for a two element + wide string character with the second element of NUL, so + when the character is unknown the minimum number of bytes + is the smaller of either 0 (at level 1) or 1 (at level 2) + and WIDTH, and the maximum is MB_CUR_MAX in the selected + locale, which is unfortunately, unknown. */ + res.range.min = 1 == warn_format_length ? !nul : nul < 1; + res.range.max = max_bytes_for_unknown_wc; + res.bounded = true; + } + else + { + /* A plain '%c' directive. */ + res.range.min = res.range.max = 1; + res.bounded = true; + res.constant = arg && TREE_CODE (arg) == INTEGER_CST; + } + } + else /* spec.specifier == 's' */ + { + /* Compute the range the argument's length can be in. */ + fmtresult slen = get_string_length (arg); + if (slen.constant) + { + gcc_checking_assert (slen.range.min == slen.range.max); + + res.bounded = true; + + /* A '%s' directive with a string argument with constant length. */ + res.range = slen.range; + + if (spec.modifier == FMT_LEN_l) + { + if (warn_format_length > 2) + { + res.range.min *= 6; + + /* It's possible to be smarter about computing the maximum + by scanning the wide string for any 8-bit characters and + if it contains none, using its length for the maximum. + Even though this would be simple to do it's unlikely to + be worth it when dealing with wide characters. */ + res.range.max *= 6; + } + /* For a wide character string, use precision as the maximum + even if precision is greater than the string length since + the number of bytes the string converts to may be greater + (due to MB_CUR_MAX). */ + if (0 <= prec) + res.range.max = prec; + } + else + res.constant = true; + + if (0 <= prec && (unsigned)prec < res.range.min) + { + res.range.min = prec; + res.range.max = prec; + } + } + else + { + /* For a '%s' and '%ls' directive with a non-constant string, + the minimum number of characters is the greater of WIDTH + and either 0 in mode 1 or the smaller of PRECISION and 1 + in mode 2, and the maximum is PRECISION or -1 to disable + tracking. */ + + if (0 <= prec) + { + if ((unsigned)prec < slen.range.min + || slen.range.min >= HOST_WIDE_INT_MAX) + slen.range.min = prec; + if ((unsigned)prec < slen.range.max + || slen.range.max >= HOST_WIDE_INT_MAX) + slen.range.max = prec; + } + else if (slen.range.min >= HOST_WIDE_INT_MAX) + { + slen.range.min = max_bytes_for_unknown_str; + slen.range.max = max_bytes_for_unknown_str; + } + + res.range = slen.range; + + /* The output is considered bounded when a precision has been + specified to limit the number of bytes or when the number + of bytes is known or contrained to some range. */ + res.bounded = 0 <= prec || slen.bounded; + res.constant = false; + } + } + + /* Adjust the lengths for field width. */ + if (res.range.min < width) + res.range.min = width; + + if (res.range.max < width) + res.range.max = width; + + /* Adjust BOUNDED if width happens to make them equal. */ + if (res.range.min == res.range.max && res.range.min < HOST_WIDE_INT_MAX) + res.bounded = true; + + return res; +} + +/* Compute the length of the output resulting from the conversion + specification SPEC with the argument ARG in a call described by INFO + and update the overall result of the call in *RES. The format directive + corresponding to SPEC starts at CVTBEG and is CVTLEN characters long. */ + +static void +format_directive (const pass_sprintf_length::call_info &info, + format_result *res, const char *cvtbeg, size_t cvtlen, + const conversion_spec &spec, tree arg) +{ + /* Offset of the beginning of the directive from the beginning + of the format string. */ + size_t offset = cvtbeg - info.fmtstr; + + /* Create a location for the whole directive from the % to the format + specifier. */ + substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format), + offset, offset, offset + cvtlen - 1); + + /* Also create a location range for the argument if possible. + This doesn't work for integer literals or function calls. */ + source_range argrange; + source_range *pargrange; + if (arg && CAN_HAVE_LOCATION_P (arg)) + { + argrange = EXPR_LOCATION_RANGE (arg); + pargrange = &argrange; + } + else + pargrange = NULL; + + /* Bail when there is no function to compute the output length, + or when minimum length checking has been disabled. */ + if (!spec.fmtfunc || res->number_chars_min >= HOST_WIDE_INT_MAX) + return; + + /* Compute the (approximate) length of the formatted output. */ + fmtresult fmtres = spec.fmtfunc (spec, arg); + + /* The overall result is bounded only if the output of every + directive is exact or bounded. */ + res->bounded = res->bounded && fmtres.bounded; + res->constant = res->constant && fmtres.constant; + + if (fmtres.range.max >= HOST_WIDE_INT_MAX) + { + /* Disable exact and maximum length checking after a failure + to determine the maximum number of characters (for example + for wide characters or wide character strings) but continue + tracking the minimum number of characters. */ + res->number_chars_max = HOST_WIDE_INT_M1U; + res->number_chars = HOST_WIDE_INT_M1U; + } + + if (fmtres.range.min >= HOST_WIDE_INT_MAX) + { + /* Disable exact length checking after a failure to determine + even the minimum number of characters (it shouldn't happen + except in an error) but keep tracking the minimum and maximum + number of characters. */ + res->number_chars = HOST_WIDE_INT_M1U; + return; + } + + /* Compute the number of available bytes in the destination. There + must always be at least one byte of space for the terminating + NUL that's appended after the format string has been processed. */ + unsigned HOST_WIDE_INT navail = min_bytes_remaining (info.objsize, *res); + + if (fmtres.range.min < fmtres.range.max) + { + /* The result is a range (i.e., it's inexact). */ + if (!res->warned) + { + bool warned = false; + + if (navail < fmtres.range.min) + { + /* The minimum directive output is longer than there is + room in the destination. */ + if (fmtres.range.min == fmtres.range.max) + { + const char* fmtstr + = (info.bounded + ? G_("%<%.*s%> directive output truncated writing " + "%wu bytes into a region of size %wu") + : G_("%<%.*s%> directive writing %wu bytes " + "into a region of size %wu")); + warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, fmtstr, + (int)cvtlen, cvtbeg, fmtres.range.min, + navail); + } + else + { + const char* fmtstr + = (info.bounded + ? G_("%<%.*s%> directive output truncated writing " + "between %wu and %wu bytes into a region of " + "size %wu") + : G_("%<%.*s%> directive writing between %wu and " + "%wu bytes into a region of size %wu")); + warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, fmtstr, + (int)cvtlen, cvtbeg, + fmtres.range.min, fmtres.range.max, navail); + } + } + else if (navail < fmtres.range.max + && (fmtres.bounded || 1 < warn_format_length)) + { + /* The maximum directive output is longer than there is + room in the destination and the output is either bounded + or the warning level is greater than 1. */ + if (fmtres.range.max >= HOST_WIDE_INT_MAX) + { + const char* fmtstr + = (info.bounded + ? G_("%<%.*s%> directive output may be truncated " + "writing %wu or more bytes a region of size %wu") + : G_("%<%.*s%> directive writing %wu or more bytes " + "into a region of size %wu")); + warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, fmtstr, + (int)cvtlen, cvtbeg, + fmtres.range.min, navail); + } + else + { + const char* fmtstr + = (info.bounded + ? G_("%<%.*s%> directive output may be truncated " + "writing between %wu and %wu bytes into a region " + "of size %wu") + : G_("%<%.*s%> directive writing between %wu and %wu " + "bytes into a region of size %wu")); + warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, fmtstr, + (int)cvtlen, cvtbeg, + fmtres.range.min, fmtres.range.max, + navail); + } + } + + res->warned |= warned; + + if (warned && fmtres.argmin) + { + if (fmtres.argmin == fmtres.argmax) + inform (info.fmtloc, "directive argument %qE", fmtres.argmin); + else if (fmtres.bounded) + inform (info.fmtloc, "directive argument in the range [%E, %E]", + fmtres.argmin, fmtres.argmax); + else + inform (info.fmtloc, + "using the range [%qE, %qE] for directive argument", + fmtres.argmin, fmtres.argmax); + } + } + + /* Disable exact length checking but adjust the minimum and maximum. */ + res->number_chars = HOST_WIDE_INT_M1U; + if (res->number_chars_max < HOST_WIDE_INT_MAX + && fmtres.range.max < HOST_WIDE_INT_MAX) + res->number_chars_max += fmtres.range.max; + + res->number_chars_min += fmtres.range.min; + } + else + { + if (!res->warned && 0 < fmtres.range.min && navail < fmtres.range.min) + { + const char* fmtstr + = (info.bounded + ? (1 < fmtres.range.min + ? G_("%<%.*s%> directive output truncated while writing " + "%wu bytes into a region of size %wu") + : G_("%<%.*s%> directive output truncated while writing " + "%wu byte into a region of size %wu")) + : (1 < fmtres.range.min + ? G_("%<%.*s%> directive writing %wu bytes " + "into a region of size %wu") + : G_("%<%.*s%> directive writing %wu byte " + "into a region of size %wu"))); + + res->warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, fmtstr, + (int)cvtlen, cvtbeg, fmtres.range.min, + navail); + } + *res += fmtres.range.min; + } + + /* Has the minimum directive output length exceeded the maximum + of 4095 bytes required to be supported? */ + bool minunder4k = fmtres.range.min < 4096; + if (!minunder4k || fmtres.range.max > 4095) + res->under4k = false; + + if (!res->warned && 1 < warn_format_length + && (!minunder4k || fmtres.range.max > 4095)) + { + /* The directive output may be longer than the maximum required + to be handled by an implementation according to 7.21.6.1, p15 + of C11. Warn on this only at level 2 but remember this and + prevent folding the return value when done. This allows for + the possibility of the actual libc call failing due to ENOMEM + (like Glibc does under some conditions). */ + + if (fmtres.range.min == fmtres.range.max) + res->warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, + "%<%.*s%> directive output of %wu bytes exceeds " + "minimum required size of 4095", + (int)cvtlen, cvtbeg, fmtres.range.min); + else + { + const char *fmtstr + = (minunder4k + ? G_("%<%.*s%> directive output between %qu and %wu " + "bytes may exceed minimum required size of 4095") + : G_("%<%.*s%> directive output between %qu and %wu " + "bytes exceeds minimum required size of 4095")); + + res->warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, fmtstr, + (int)cvtlen, cvtbeg, + fmtres.range.min, fmtres.range.max); + } + } + + /* Has the minimum directive output length exceeded INT_MAX? */ + bool exceedmin = res->number_chars_min > target_int_max (); + + if (!res->warned + && (exceedmin + || (1 < warn_format_length + && res->number_chars_max > target_int_max ()))) + { + /* The directive output causes the total length of output + to exceed INT_MAX bytes. */ + + if (fmtres.range.min == fmtres.range.max) + res->warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, + "%<%.*s%> directive output of %wu bytes causes " + "result to exceed %", + (int)cvtlen, cvtbeg, fmtres.range.min); + else + { + const char *fmtstr + = (exceedmin + ? G_ ("%<%.*s%> directive output between %wu and %wu " + "bytes causes result to exceed %") + : G_ ("%<%.*s%> directive output between %wu and %wu " + "bytes may cause result to exceed %")); + res->warned = fmtwarn (dirloc, pargrange, NULL, + OPT_Wformat_length_, fmtstr, + (int)cvtlen, cvtbeg, + fmtres.range.min, fmtres.range.max); + } + } +} + +/* Account for the number of bytes between BEG and END (or between + BEG + strlen (BEG) when END is null) in the format string in a call + to a formatted output function described by INFO. Reflect the count + in RES and issue warnings as appropriate. */ + +static void +add_bytes (const pass_sprintf_length::call_info &info, + const char *beg, const char *end, format_result *res) +{ + if (res->number_chars_min >= HOST_WIDE_INT_MAX) + return; + + /* The number of bytes to output is the number of bytes between + the end of the last directive and the beginning of the next + one if it exists, otherwise the number of characters remaining + in the format string plus 1 for the terminating NUL. */ + size_t nbytes = end ? end - beg : strlen (beg) + 1; + + /* Return if there are no bytes to add at this time but there are + directives remaining in the format string. */ + if (!nbytes) + return; + + /* Compute the range of available bytes in the destination. There + must always be at least one byte left for the terminating NUL + that's appended after the format string has been processed. */ + result_range avail_range = bytes_remaining (info.objsize, *res); + + /* If issuing a diagnostic (only when one hasn't already been issued), + distinguish between a possible overflow ("may write") and a certain + overflow somewhere "past the end." (Ditto for truncation.) */ + if (!res->warned + && (avail_range.max < nbytes + || ((res->bounded || 1 < warn_format_length) + && avail_range.min < nbytes))) + { + /* Set NAVAIL to the number of available bytes used to decide + whether or not to issue a warning below. The exact kind of + warning will depend on AVAIL_RANGE. */ + unsigned HOST_WIDE_INT navail = avail_range.max; + if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX + && (res->bounded || 1 < warn_format_length)) + navail = avail_range.min; + + /* Compute the offset of the first format character that is beyond + the end of the destination region and the length of the rest of + the format string from that point on. */ + unsigned HOST_WIDE_INT off + = (unsigned HOST_WIDE_INT)(beg - info.fmtstr) + navail; + + size_t len = strlen (info.fmtstr + off); + + substring_loc loc + (info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0, + off + len - !!len); + + /* Is the output of the last directive the result of the argument + being within a range whose lower bound would fit in the buffer + but the upper bound would not? If so, use the word "may" to + indicate that the overflow/truncation may (but need not) happen. */ + bool boundrange + = (res->number_chars_min < res->number_chars_max + && res->number_chars_min < info.objsize); + + if (!end && (nbytes - navail) == 1) + { + /* There is room for the rest of the format string but none + for the terminating nul. */ + const char *text + = (info.bounded // Snprintf and the like. + ? (boundrange + ? G_("output may be truncated before the last format character" + : "output truncated before the last format character")) + : (boundrange + ? G_("may write a terminating nul past the end " + "of the destination") + : G_("writing a terminating nul past the end " + "of the destination"))); + + res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_, text); + } + else + { + /* There isn't enough room for 1 or more characters that remain + to copy from the format string. */ + const char *text + = (info.bounded // Snprintf and the like. + ? (boundrange + ? G_("output may be truncated at or before format character " + "%qc at offset %wu") + : G_("output truncated at format character %qc at offset %wu")) + : (res->number_chars >= HOST_WIDE_INT_MAX + ? G_("may write format character %#qc at offset %wu past " + "the end of the destination") + : G_("writing format character %#qc at offset %wu past " + "the end of the destination"))); + + res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_, + text, info.fmtstr[off], off); + } + } + + if (res->warned && !end && info.objsize < HOST_WIDE_INT_MAX) + { + /* If a warning has been issued for buffer overflow or truncation + (but not otherwise) help the user figure out how big a buffer + they need. */ + + location_t callloc = gimple_location (info.callstmt); + + unsigned HOST_WIDE_INT min = res->number_chars_min; + unsigned HOST_WIDE_INT max = res->number_chars_max; + unsigned HOST_WIDE_INT exact + = (res->number_chars < HOST_WIDE_INT_MAX + ? res->number_chars : res->number_chars_min); + + if (min < max && max < HOST_WIDE_INT_MAX) + inform (callloc, + "format output between %wu and %wu bytes into " + "a destination of size %wu", + min + nbytes, max + nbytes, info.objsize); + else + inform (callloc, + (nbytes + exact == 1 + ? G_("format output %wu byte into a destination of size %wu") + : G_("format output %wu bytes into a destination of size %wu")), + nbytes + exact, info.objsize); + } + + /* Add the number of bytes and then check for INT_MAX overflow. */ + *res += nbytes; + + /* Has the minimum output length minus the terminating nul exceeded + INT_MAX? */ + bool exceedmin = (res->number_chars_min - !end) > target_int_max (); + + if (!res->warned + && (exceedmin + || (1 < warn_format_length + && (res->number_chars_max - !end) > target_int_max ()))) + { + /* The function's output exceeds INT_MAX bytes. */ + + /* Set NAVAIL to the number of available bytes used to decide + whether or not to issue a warning below. The exact kind of + warning will depend on AVAIL_RANGE. */ + unsigned HOST_WIDE_INT navail = avail_range.max; + if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX + && (res->bounded || 1 < warn_format_length)) + navail = avail_range.min; + + /* Compute the offset of the first format character that is beyond + the end of the destination region and the length of the rest of + the format string from that point on. */ + unsigned HOST_WIDE_INT off = (unsigned HOST_WIDE_INT)(beg - info.fmtstr); + if (navail < HOST_WIDE_INT_MAX) + off += navail; + + size_t len = strlen (info.fmtstr + off); + + substring_loc loc + (info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0, + off + len - !!len); + + if (res->number_chars_min == res->number_chars_max) + res->warned = fmtwarn (loc, NULL, NULL, + OPT_Wformat_length_, + "output of %wu bytes causes " + "result to exceed %", + res->number_chars_min - !end); + else + { + const char *text + = (exceedmin + ? G_ ("output between %wu and %wu bytes causes " + "result to exceed %") + : G_ ("output between %wu and %wu bytes may cause " + "result to exceed %")); + res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_, + text, + res->number_chars_min - !end, + res->number_chars_max - !end); + } + } +} + +#pragma GCC diagnostic pop + +/* Compute the length of the output resulting from the call to a formatted + output function described by INFO and store the result of the call in + *RES. Issue warnings for detected past the end writes. */ + +void +pass_sprintf_length::compute_format_length (const call_info &info, + format_result *res) +{ + /* The variadic argument counter. */ + unsigned argno = info.argidx; + + /* Reset exact, minimum, and maximum character counters. */ + res->number_chars = res->number_chars_min = res->number_chars_max = 0; + + /* No directive has been seen yet so the output is bounded and constant + (with no conversion producing more than 4K bytes) until determined + otherwise. */ + res->bounded = true; + res->constant = true; + res->under4k = true; + res->floating = false; + res->warned = false; + + const char *pf = info.fmtstr; + + for ( ; ; ) + { + /* The beginning of the next format directive. */ + const char *dir = strchr (pf, '%'); + + /* Add the number of bytes between the end of the last directive + and either the next if one exists, or the end of the format + string. */ + add_bytes (info, pf, dir, res); + + if (!dir) + break; + + pf = dir + 1; + + if (0 && *pf == 0) + { + /* Incomplete directive. */ + return; + } + + conversion_spec spec = conversion_spec (); + + /* POSIX numbered argument index or zero when none. */ + unsigned dollar = 0; + + if (ISDIGIT (*pf)) + { + /* This could be either a POSIX positional argument, the '0' + flag, or a width, depending on what follows. Store it as + width and sort it out later after the next character has + been seen. */ + char *end; + spec.width = strtol (pf, &end, 10); + spec.have_width = true; + pf = end; + } + else if ('*' == *pf) + { + /* Similarly to the block above, this could be either a POSIX + positional argument or a width, depending on what follows. */ + if (argno < gimple_call_num_args (info.callstmt)) + spec.star_width = gimple_call_arg (info.callstmt, argno++); + else + return; + ++pf; + } + + if (*pf == '$') + { + /* Handle the POSIX dollar sign which references the 1-based + positional argument number. */ + if (spec.have_width) + dollar = spec.width + info.argidx; + else if (spec.star_width + && TREE_CODE (spec.star_width) == INTEGER_CST) + dollar = spec.width + tree_to_shwi (spec.star_width); + + /* Bail when the numbered argument is out of range (it will + have already been diagnosed by -Wformat). */ + if (dollar == 0 + || dollar == info.argidx + || dollar > gimple_call_num_args (info.callstmt)) + return; + + --dollar; + + spec.star_width = NULL_TREE; + spec.have_width = false; + ++pf; + } + + if (dollar || !spec.star_width) + { + if (spec.have_width && spec.width == 0) + { + /* The '0' that has been interpreted as a width above is + actually a flag. Reset HAVE_WIDTH, set the '0' flag, + and continue processing other flags. */ + spec.have_width = false; + spec.set_flag ('0'); + } + /* When either '$' has been seen, or width has not been seen, + the next field is the optional flags followed by an optional + width. */ + for ( ; ; ) { + switch (*pf) + { + case ' ': + case '0': + case '+': + case '-': + case '#': + spec.set_flag (*pf++); + break; + + default: + goto start_width; + } + } + + start_width: + if (ISDIGIT (*pf)) + { + char *end; + spec.width = strtol (pf, &end, 10); + spec.have_width = true; + pf = end; + } + else if ('*' == *pf) + { + spec.star_width = gimple_call_arg (info.callstmt, argno++); + ++pf; + } + else if ('\'' == *pf) + { + /* The POSIX apostrophe indicating a numeric grouping + in the current locale. Even though it's possible to + estimate the upper bound on the size of the output + based on the number of digits it probably isn't worth + continuing. */ + return; + } + } + + if ('.' == *pf) + { + ++pf; + + if (ISDIGIT (*pf)) + { + char *end; + spec.precision = strtol (pf, &end, 10); + spec.have_precision = true; + pf = end; + } + else if ('*' == *pf) + { + spec.star_precision = gimple_call_arg (info.callstmt, argno++); + ++pf; + } + else + return; + } + + switch (*pf) + { + case 'h': + if (pf[1] == 'h') + { + ++pf; + spec.modifier = FMT_LEN_hh; + } + else + spec.modifier = FMT_LEN_h; + ++pf; + break; + + case 'j': + spec.modifier = FMT_LEN_j; + ++pf; + break; + + case 'L': + spec.modifier = FMT_LEN_L; + ++pf; + break; + + case 'l': + if (pf[1] == 'l') + { + ++pf; + spec.modifier = FMT_LEN_ll; + } + else + spec.modifier = FMT_LEN_l; + ++pf; + break; + + case 't': + spec.modifier = FMT_LEN_t; + ++pf; + break; + + case 'z': + spec.modifier = FMT_LEN_z; + ++pf; + break; + } + + switch (*pf) + { + /* Handle a sole '%' character the same as "%%" but since it's + undefined prevent the result from being folded. */ + case '\0': + --pf; + res->bounded = false; + case '%': + spec.fmtfunc = format_percent; + break; + + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + res->floating = true; + spec.fmtfunc = format_floating; + break; + + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + spec.fmtfunc = format_integer; + break; + + case 'p': + spec.fmtfunc = format_pointer; + break; + + case 'n': + return; + + case 'c': + case 'S': + case 's': + spec.fmtfunc = format_string; + break; + + default: + return; + } + + spec.specifier = *pf++; + + /* Compute the length of the format directive. */ + size_t dirlen = pf - dir; + + /* Extract the argument if the directive takes one and if it's + available (e.g., the function doesn't take a va_list). Treat + missing arguments the same as va_list, even though they will + have likely already been diagnosed by -Wformat. */ + tree arg = NULL_TREE; + if (spec.specifier != '%' + && argno < gimple_call_num_args (info.callstmt)) + arg = gimple_call_arg (info.callstmt, dollar ? dollar : argno++); + + ::format_directive (info, res, dir, dirlen, spec, arg); + } +} + +/* Return the size of the object referenced by the expression DEST if + available, or -1 otherwise. */ + +static unsigned HOST_WIDE_INT +get_destination_size (tree dest) +{ + /* Use __builtin_object_size to determine the size of the destination + object. When optimizing, determine the smallest object (such as + a member array as opposed to the whole enclosing object), otherwise + use type-zero object size to determine the size of the enclosing + object (the function fails without optimization in this type). */ + int ost = 0 < optimize; + unsigned HOST_WIDE_INT size; + if (compute_builtin_object_size (dest, ost, &size)) + return size; + + return HOST_WIDE_INT_M1U; +} + +/* Given a suitable result RES of a call to a formatted output function + described by INFO, substitute the result for the return value of + the call. The result is suitable if the number of bytes it represents + is known and exact. A result that isn't suitable for substitution may + have its range set to the range of return values, if that is known. */ + +static void +try_substitute_return_value (gimple_stmt_iterator gsi, + const pass_sprintf_length::call_info &info, + const format_result &res) +{ + tree lhs = gimple_get_lhs (info.callstmt); + + /* Avoid the return value optimization when the behavior of the call + is undefined either because any directive may have produced 4K or + more of output, or the return value exceeds INT_MAX, or because + the output overflows the destination object (but leave it enabled + when the function is bounded because then the behavior is well- + defined). */ + if (lhs && res.bounded && res.under4k + && (info.bounded || res.number_chars <= info.objsize) + && res.number_chars - 1 <= target_int_max ()) + { + /* Replace the left-hand side of the call with the constant + result of the formatted function minus 1 for the terminating + NUL which the functions' return value does not include. */ + gimple_call_set_lhs (info.callstmt, NULL_TREE); + tree cst = build_int_cst (integer_type_node, res.number_chars - 1); + gimple *g = gimple_build_assign (lhs, cst); + gsi_insert_after (&gsi, g, GSI_NEW_STMT); + update_stmt (info.callstmt); + + if (dump_file) + { + location_t callloc = gimple_location (info.callstmt); + fprintf (dump_file, "On line %i substituting ", + LOCATION_LINE (callloc)); + print_generic_expr (dump_file, cst, dump_flags); + fprintf (dump_file, " for "); + print_generic_expr (dump_file, info.func, dump_flags); + fprintf (dump_file, " return value (output %s).\n", + res.constant ? "constant" : "variable"); + } + } + else + { + unsigned HOST_WIDE_INT maxbytes; + + if (lhs + && ((maxbytes = res.number_chars - 1) <= target_int_max () + || (res.number_chars_min - 1 <= target_int_max () + && (maxbytes = res.number_chars_max - 1) <= target_int_max ())) + && (info.bounded || maxbytes < info.objsize)) + { + /* If the result is in a valid range bounded by the size of + the destination set it so that it can be used for subsequent + optimizations. */ + int prec = TYPE_PRECISION (integer_type_node); + + if (res.number_chars < target_int_max () && res.under4k) + { + wide_int num = wi::shwi (res.number_chars - 1, prec); + set_range_info (lhs, VR_RANGE, num, num); + } + else if (res.number_chars_min < target_int_max () + && res.number_chars_max < target_int_max ()) + { + wide_int min = wi::shwi (res.under4k ? res.number_chars_min - 1 + : target_int_min (), prec); + wide_int max = wi::shwi (res.number_chars_max - 1, prec); + set_range_info (lhs, VR_RANGE, min, max); + } + } + + if (dump_file) + { + const char *inbounds + = (res.number_chars_min <= info.objsize + ? (res.number_chars_max <= info.objsize + ? "in" : "potentially out-of") + : "out-of"); + + location_t callloc = gimple_location (info.callstmt); + fprintf (dump_file, "On line %i ", LOCATION_LINE (callloc)); + print_generic_expr (dump_file, info.func, dump_flags); + + const char *ign = lhs ? "" : " ignored"; + if (res.number_chars >= HOST_WIDE_INT_MAX) + fprintf (dump_file, + " %s-bounds return value in range [%lu, %lu]%s.\n", + inbounds, + (unsigned long)res.number_chars_min, + (unsigned long)res.number_chars_max, ign); + else + fprintf (dump_file, " %s-bounds return value %lu%s.\n", + inbounds, (unsigned long)res.number_chars, ign); + } + } +} + +/* Determine if a GIMPLE CALL is to one of the sprintf-like built-in + functions and if so, handle it. */ + +void +pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator gsi) +{ + call_info info = call_info (); + + info.callstmt = gsi_stmt (gsi); + info.func = gimple_call_fn (info.callstmt); + if (!info.func) + return; + + if (TREE_CODE (info.func) == ADDR_EXPR) + info.func = TREE_OPERAND (info.func, 0); + + if (TREE_CODE (info.func) != FUNCTION_DECL + || !DECL_BUILT_IN(info.func) + || DECL_BUILT_IN_CLASS (info.func) != BUILT_IN_NORMAL) + return; + + info.fncode = DECL_FUNCTION_CODE (info.func); + + /* 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; + + /* Object size argument number (snprintf_chk and vsnprintf_chk). */ + unsigned HOST_WIDE_INT idx_objsize = HOST_WIDE_INT_M1U; + + /* Format string argument number (valid for all functions). */ + unsigned idx_format; + + switch (info.fncode) + { + case BUILT_IN_SPRINTF: + // Signature: + // __builtin_sprintf (dst, format, ...) + idx_format = 1; + info.argidx = 2; + break; + + case BUILT_IN_SNPRINTF: + // Signature: + // __builtin_snprintf (dst, size, format, ...) + idx_dstsize = 1; + idx_format = 2; + info.argidx = 3; + info.bounded = true; + break; + + case BUILT_IN_SNPRINTF_CHK: + // Signature: + // __builtin___sprintf_chk (dst, size, ost, objsize, format, ...) + idx_dstsize = 1; + idx_objsize = 3; + idx_format = 4; + info.argidx = 5; + info.bounded = true; + break; + + case BUILT_IN_SPRINTF_CHK: + // Signature: + // __builtin___sprintf_chk (dst, ost, objsize, format, ...) + idx_objsize = 2; + idx_format = 3; + info.argidx = 4; + break; + + case BUILT_IN_VSNPRINTF: + // Signature: + // __builtin_vsprintf (dst, size, format, va) + idx_dstsize = 1; + idx_format = 2; + info.argidx = -1; + info.bounded = true; + break; + + case BUILT_IN_VSNPRINTF_CHK: + // Signature: + // __builtin___vsnprintf_chk (dst, size, ost, objsize, format, va) + idx_dstsize = 1; + idx_objsize = 2; + idx_format = 3; + info.argidx = -1; + info.bounded = true; + break; + + case BUILT_IN_VSPRINTF: + // Signature: + // __builtin_vsprintf (dst, format, va) + idx_format = 1; + info.argidx = -1; + break; + + case BUILT_IN_VSPRINTF_CHK: + // Signature: + // __builtin___vsprintf_chk (dst, ost, objsize, format, va) + idx_format = 3; + idx_objsize = 2; + info.argidx = -1; + break; + + default: + return; + } + + info.format = gimple_call_arg (info.callstmt, idx_format); + + if (idx_dstsize == HOST_WIDE_INT_M1U) + { + // For non-bounded functions like sprintf, to to determine + // the size of the destination from the object or pointer + // passed to it as the first argument. + dstsize = get_destination_size (gimple_call_arg (info.callstmt, 0)); + } + else if (tree size = gimple_call_arg (info.callstmt, idx_dstsize)) + { + /* For bounded functions try to get the size argument. */ + + if (TREE_CODE (size) == INTEGER_CST) + { + dstsize = tree_to_uhwi (size); + /* No object can be larger than HOST_WIDE_INT_MAX bytes + (half the address space). This imposes a limit that's + one byte less than that. */ + if (dstsize >= HOST_WIDE_INT_MAX) + warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, + "specified destination size %wu too large", + dstsize); + } + else if (TREE_CODE (size) == SSA_NAME) + { + /* Try to determine the range of values of the argument + and use the greater of the two at -Wformat-level 1 and + the smaller of them at level 2. */ + wide_int min, max; + enum value_range_type range_type + = get_range_info (size, &min, &max); + if (range_type == VR_RANGE) + { + dstsize + = (warn_format_length < 2 + ? wi::fits_uhwi_p (max) ? max.to_uhwi () : max.to_shwi () + : wi::fits_uhwi_p (min) ? min.to_uhwi () : min.to_shwi ()); + } + } + } + + if (idx_objsize != HOST_WIDE_INT_M1U) + { + if (tree size = gimple_call_arg (info.callstmt, idx_objsize)) + if (tree_fits_uhwi_p (size)) + objsize = tree_to_uhwi (size); + } + + if (info.bounded && !dstsize) + { + /* As a special case, when the explicitly specified destination + size argument (to a bounded function like snprintf) is zero + it is a request to determine the number of bytes on output + without actually producing any. Pretend the size is + unlimited in this case. */ + info.objsize = HOST_WIDE_INT_MAX; + } + else + { + /* Set the object size to the smaller of the two arguments + of both have been specified and they're not equal. */ + info.objsize = dstsize < objsize ? dstsize : objsize; + + if (info.bounded + && dstsize != HOST_WIDE_INT_M1U && objsize < dstsize) + { + warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, + "specified size %wu exceeds the size %wu " + "of the destination object", dstsize, objsize); + } + } + + if (integer_zerop (info.format)) + { + /* This is diagnosed with -Wformat only when the null is a constant + pointer. The warning here diagnoses instances where the pointer + is not constant. */ + warning_at (EXPR_LOC_OR_LOC (info.format, input_location), + OPT_Wformat_length_, "null format string"); + return; + } + + info.fmtstr = get_format_string (info.format, &info.fmtloc); + if (!info.fmtstr) + return; + + /* The result is the number of bytes output by the formatted function, + including the terminating NUL. */ + format_result res = format_result (); + compute_format_length (info, &res); + + /* When optimizing and the printf return value optimization is enabled, + attempt to substitute the computed result for the return value of + the call. Avoid this optimization when -frounding-math is in effect + and the format string contains a floating point directive. */ + if (0 < optimize && flag_printf_return_value + && (!flag_rounding_math || !res.floating)) + try_substitute_return_value (gsi, info, res); +} + +/* Execute the pass for function FUN. */ + +unsigned int +pass_sprintf_length::execute (function *fun) +{ + basic_block bb; + FOR_EACH_BB_FN (bb, fun) + { + for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si); + gsi_next (&si)) + { + /* Iterate over statements, looking for function calls. */ + gimple *stmt = gsi_stmt (si); + + if (gimple_code (stmt) == GIMPLE_CALL) + handle_gimple_call (si); + } + } + + return 0; +} + +} /* Unnamed namespace. */ + +/* Return a pointer to a pass object newly constructed from the context + CTXT. */ + +gimple_opt_pass * +make_pass_sprintf_length (gcc::context *ctxt) +{ + return new pass_sprintf_length (ctxt); +} diff --git a/gcc/passes.def b/gcc/passes.def index 6a83371f8c2..1375254e971 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -43,6 +43,7 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_warn_function_return); NEXT_PASS (pass_expand_omp); NEXT_PASS (pass_build_cgraph_edges); + NEXT_PASS (pass_sprintf_length, false); TERMINATE_PASS_LIST (all_lowering_passes) /* Interprocedural optimization passes. */ @@ -306,6 +307,7 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_simduid_cleanup); NEXT_PASS (pass_lower_vector_ssa); NEXT_PASS (pass_cse_reciprocals); + NEXT_PASS (pass_sprintf_length, true); NEXT_PASS (pass_reassoc, false /* insert_powi_p */); NEXT_PASS (pass_strength_reduction); NEXT_PASS (pass_split_paths); diff --git a/gcc/print-tree.c b/gcc/print-tree.c index e55b6bd6fb6..60957f95259 100644 --- a/gcc/print-tree.c +++ b/gcc/print-tree.c @@ -769,7 +769,8 @@ print_node (FILE *file, const char *prefix, tree node, int indent) case VECTOR_CST: { - char buf[10]; + /* Big enough for 2 UINT_MAX plus the string below. */ + char buf[32]; unsigned i; for (i = 0; i < VECTOR_CST_NELTS (node); ++i) diff --git a/gcc/target.def b/gcc/target.def index 0e10d6f03c8..33acc79d5ed 100644 --- a/gcc/target.def +++ b/gcc/target.def @@ -3353,6 +3353,12 @@ greater than 128 and a multiple of 32.", machine_mode, (int n, bool extended), default_floatn_mode) +DEFHOOK +(printf_pointer_format, + "Determine the target @code{printf} implementation format string that the most closely corresponds to the @code{%p} format directive. The object pointed to by the @var{flags} is set to a string consisting of recognized format flags such as the @code{'#'} character.", + const char*, (tree, const char **flags), + default_printf_pointer_format) + /* Compute cost of moving data from a register of class FROM to one of TO, using MODE. */ DEFHOOK diff --git a/gcc/targhooks.c b/gcc/targhooks.c index 67cc53a2641..d75650fede3 100644 --- a/gcc/targhooks.c +++ b/gcc/targhooks.c @@ -1509,6 +1509,20 @@ no_c99_libc_has_function (enum function_class fn_class ATTRIBUTE_UNUSED) return false; } +/* Return the format string to which "%p" corresponds. By default, + assume it corresponds to the C99 "%zx" format and set *FLAGS to + the empty string to indicate that format flags have no effect. + An example of an implementation that matches this description + is AIX. */ + +const char* +default_printf_pointer_format (tree, const char **flags) +{ + *flags = ""; + + return "%zx"; +} + tree default_builtin_tm_load_store (tree ARG_UNUSED (type)) { diff --git a/gcc/targhooks.h b/gcc/targhooks.h index a2fa49f89b3..3356f0afe0f 100644 --- a/gcc/targhooks.h +++ b/gcc/targhooks.h @@ -191,6 +191,10 @@ extern bool default_libc_has_function (enum function_class); extern bool no_c99_libc_has_function (enum function_class); extern bool gnu_libc_has_function (enum function_class); +extern const char* default_printf_pointer_format (tree, const char **); +extern const char* gnu_libc_printf_pointer_format (tree, const char **); +extern const char* solaris_printf_pointer_format (tree, const char **); + extern tree default_builtin_tm_load_store (tree); extern int default_memory_move_cost (machine_mode, reg_class_t, bool); diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 006607b19ab..6d26e43fcef 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,14 @@ +2016-09-20 Martin Sebor + + PR middle-end/49905 + * gcc.dg/builtin-stringop-chk-1.c: Adjust. + * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: New test. + * gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: New test. + * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: New test. + * gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: New test. + * gcc.dg/tree-ssa/builtin-sprintf.c: New test. + * gcc.dg/tree-ssa/builtin-sprintf-2.c: New test. + 2016-09-21 Kugan Vivekanandarajah * gcc.dg/guality/pr54519-1.c: Add -fno-ipa-vrp. Else constant diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c index 6e71aeeec28..e491ff52680 100644 --- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c @@ -1,7 +1,7 @@ /* Test whether buffer overflow warnings for __*_chk builtins are emitted properly. */ /* { dg-do compile } */ -/* { dg-options "-O2 -std=gnu99 -ftrack-macro-expansion=0" } */ +/* { dg-options "-O2 -Wno-format -std=gnu99 -ftrack-macro-expansion=0" } */ /* { dg-additional-options "-mstructure-size-boundary=8" { target arm*-*-* } } */ // { dg-skip-if "packed attribute missing for t" { "epiphany-*-*" } { "*" } { "" } } diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c new file mode 100644 index 00000000000..f7abfd8ba0d --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c @@ -0,0 +1,218 @@ +/* Test to verify that the return value of calls to __builtin_sprintf + is not folded if the call has undefined behavior even if it would + otherwise produce a known number of bytes on output, and that if + the return value is in a known range the range is not made + available to subsequent passes and doesn't affect branching and + the removal of code. + The test is compiled with warnings disabled to make sure the absence + of optimizations does not depend on the presence of warnings. */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fprintf-return-value -fdump-tree-optimized -ftrack-macro-expansion=0 -w" } */ + +#ifndef LINE +# define LINE 0 +#endif + +#define INT_MAX __INT_MAX__ + +char *buf; +char buf8k [8192]; + +#define concat(a, b) a ## b +#define CAT(a, b) concat (a, b) + +#define EQL(expect, size, fmt, ...) \ + void CAT (test_on_line_, __LINE__)(void) \ + { \ + if (!LINE || LINE == __LINE__) \ + { \ + char *dst = size < 0 ? buf : buf8k + sizeof buf8k - size; \ + int result = __builtin_sprintf (dst, fmt, __VA_ARGS__); \ + if (result != expect) \ + __builtin_abort (); \ + } \ + } + +/* Verify that the return value or range or return values from the call + to the formatted function is not treated as a constant or made available + to subsequent optimization passes. */ +#define RNG(min, max, size, fmt, ...) \ + void CAT (test_on_line_, __LINE__)(void) \ + { \ + if (!LINE || LINE == __LINE__) \ + { \ + char *dst = size < 0 ? buf : buf8k + sizeof buf8k - size; \ + int result = __builtin_sprintf (dst, fmt, __VA_ARGS__); \ + if (result < min || max < result) \ + __builtin_abort (); \ + } \ + } + +extern int i; +extern long li; +extern char *str; + +/* Verify that overflowing the destination object disables the return + value optimization. */ +EQL (0, 0, "%c", ' '); +EQL (0, 0, "%c", i) +EQL (0, 0, "%-s", ""); + +EQL (1, 1, "%c", 'x'); +EQL (1, 1, "%-s", "x"); + +EQL (4, 4, "%4c", 'x'); + +/* Verify that exceeding the environmental limit of 4095 bytes for + a single conversion specification disables the return value + folding. */ +EQL ( 4096, sizeof buf8k, "%4096c", 'x'); + +EQL (INT_MAX, -1, "%*c", INT_MAX, 'x'); + +EQL ( 4096, sizeof buf8k, "%4096.4094f", 1.0); +EQL ( 4096, sizeof buf8k, "%.4094f", 1.0); +EQL ( 4097, sizeof buf8k, "%.4095f", 1.0); + +enum { imax2 = (INT_MAX / 2) * 2 }; +EQL (imax2, -1, "%*c%*c", INT_MAX / 2, 'x', INT_MAX / 2, 'y'); + +/* Verify that range inforation for calls that overflow the destination + isn't available. */ +RNG (0, 0, 0, "%hhi", i) +RNG (0, 0, 1, "%hhi", i) +RNG (0, 1, 1, "%hhi", i) +RNG (0, 0, 2, "%hhi", i) +RNG (0, 1, 2, "%hhi", i) +RNG (0, 2, 2, "%hhi", i) +RNG (0, 0, 3, "%hhi", i) +RNG (0, 1, 3, "%hhi", i) +RNG (0, 2, 3, "%hhi", i) +RNG (0, 3, 3, "%hhi", i) +RNG (0, 0, 4, "%hhi", i) +RNG (0, 1, 4, "%hhi", i) +RNG (0, 2, 4, "%hhi", i) +RNG (0, 3, 4, "%hhi", i) +RNG (0, 4, 4, "%hhi", i) + +RNG (0, 0, 0, "%hhu", i) +RNG (0, 0, 1, "%hhu", i) +RNG (0, 1, 1, "%hhu", i) +RNG (0, 0, 2, "%hhu", i) +RNG (0, 1, 2, "%hhu", i) +RNG (0, 2, 2, "%hhu", i) +RNG (0, 0, 3, "%hhu", i) +RNG (0, 1, 3, "%hhu", i) +RNG (0, 2, 3, "%hhu", i) +RNG (0, 3, 3, "%hhu", i) + +RNG (0, 0, 0, "%i", i) + +RNG (0, 0, 1, "%i", i) +RNG (0, 1, 1, "%i", i) + +RNG (0, 0, 2, "%i", i) +RNG (0, 1, 2, "%i", i) +RNG (0, 2, 2, "%i", i) + +RNG (0, 0, 3, "%i", i) +RNG (0, 1, 3, "%i", i) +RNG (0, 2, 3, "%i", i) +RNG (0, 3, 3, "%i", i) + +RNG (0, 0, 4, "%i", i) +RNG (0, 1, 4, "%i", i) +RNG (0, 2, 4, "%i", i) +RNG (0, 3, 4, "%i", i) +RNG (0, 4, 4, "%i", i) + +RNG (0, 0, 5, "%i", i) +RNG (0, 1, 5, "%i", i) +RNG (0, 2, 5, "%i", i) +RNG (0, 3, 5, "%i", i) +RNG (0, 4, 5, "%i", i) +RNG (0, 5, 5, "%i", i) + +RNG (0, 0, 6, "%i", i) +RNG (0, 1, 6, "%i", i) +RNG (0, 2, 6, "%i", i) +RNG (0, 3, 6, "%i", i) +RNG (0, 4, 6, "%i", i) +RNG (0, 5, 6, "%i", i) +RNG (0, 6, 6, "%i", i) + +RNG (0, 0, 7, "%i", i) +RNG (0, 1, 7, "%i", i) +RNG (0, 2, 7, "%i", i) +RNG (0, 3, 7, "%i", i) +RNG (0, 4, 7, "%i", i) +RNG (0, 5, 7, "%i", i) +RNG (0, 6, 7, "%i", i) + +#if __SIZEOF_INT__ == 4 + +/* A 32-bit int takes up at most 11 bytes (-2147483648) not including + the terminating nul. */ +RNG (0, 7, 7, "%i", i) + +RNG (0, 0, 8, "%i", i) +RNG (0, 1, 8, "%i", i) +RNG (0, 2, 8, "%i", i) +RNG (0, 3, 8, "%i", i) +RNG (0, 4, 8, "%i", i) +RNG (0, 5, 8, "%i", i) +RNG (0, 6, 8, "%i", i) +RNG (0, 7, 8, "%i", i) +RNG (0, 8, 8, "%i", i) + +RNG (0, 0, 9, "%i", i) +RNG (0, 1, 9, "%i", i) +RNG (0, 2, 9, "%i", i) +RNG (0, 3, 9, "%i", i) +RNG (0, 4, 9, "%i", i) +RNG (0, 5, 9, "%i", i) +RNG (0, 6, 9, "%i", i) +RNG (0, 7, 9, "%i", i) +RNG (0, 8, 9, "%i", i) +RNG (0, 9, 9, "%i", i) + +RNG (0, 0, 10, "%i", i) +RNG (0, 1, 10, "%i", i) +RNG (0, 2, 10, "%i", i) +RNG (0, 3, 10, "%i", i) +RNG (0, 4, 10, "%i", i) +RNG (0, 5, 10, "%i", i) +RNG (0, 6, 10, "%i", i) +RNG (0, 7, 10, "%i", i) +RNG (0, 8, 10, "%i", i) +RNG (0, 9, 10, "%i", i) +RNG (0, 10, 10, "%i", i) + +#endif + +/* Verify the result of a conditional expression involving a string + literal and an unknown string isn't optimized. */ +RNG (0, 1, 4, "%-s", i ? str : "123"); +RNG (0, 1, 4, "%-s", i ? "123" : str); + +/* Verify that no call to abort has been eliminated and that each call + is at the beginning of a basic block (and thus the result of a branch). + This latter test tries to verify that the test preceding the call to + abort has not been eliminated either. + + The expected output looks something like this: + + : + result_3 = __builtin_sprintf (&MEM[(void *)&buf8k + 8192B], "%c", 32); + if (result_3 != 0) + goto ; + else + goto ; + + : + __builtin_abort (); + +*/ + +/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 105 "optimized" { target { ilp32 || lp64 } } } } */ +/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 74 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c new file mode 100644 index 00000000000..7261dbdc4a8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c @@ -0,0 +1,1417 @@ +/* { dg-do compile } */ +/* { dg-options "-std=c99 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */ + +/* 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__ + +char buffer [256]; +extern char *ptr; + +/* Evaluate to an array of SIZE characters when non-negative and LINE + is not set or set to the line the macro is on, or to a pointer to + an unknown object otherwise. */ +#define buffer(size) \ + (0 <= size && (!LINE || __LINE__ == LINE) \ + ? buffer + sizeof buffer - size : ptr) + +/* Evaluate to SIZE when non-negative and LINE is not set or set to + the line the macro is on, or to SIZE_MAX otherise. */ +#define objsize(size) \ + (0 <= size && (!LINE || __LINE__ == LINE) \ + ? size : __SIZE_MAX__) + +typedef __SIZE_TYPE__ size_t; + +#if !__cplusplus +typedef __WCHAR_TYPE__ wchar_t; +#endif + +typedef unsigned char UChar; + +const char s0[] = ""; +const char s1[] = "1"; +const char s2[] = "12"; +const char s3[] = "123"; +const char s4[] = "1234"; +const char s5[] = "12345"; +const char s6[] = "123456"; +const char s7[] = "1234567"; +const char s8[] = "12345678"; + +void sink (void*, ...); + +/* Macro to verify that calls to __builtin_sprintf (i.e., with no size + argument) issue diagnostics by correctly determining the size of + the destination buffer. */ +#define T(size, fmt, ...) \ + __builtin_sprintf (buffer (size), fmt, __VA_ARGS__), \ + sink (buffer, ptr); + +/* Exercise the "%c" and "%lc" directive with constant arguments. */ + +void test_sprintf_c_const (void) +{ + T (-1, "%c", 0); /* No warning for unknown destination size. */ + T ( 0, "%c", 0); /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */ + T ( 1, "%c", 0); /* { dg-warning "writing a terminating nul past the end" } */ + T ( 1, "%c", '1'); /* { dg-warning "nul past the end" } */ + T ( 2, "%c", '1'); + T ( 2, "%2c", '1'); /* { dg-warning "nul past the end" } */ + T ( 2, "%3c", '1'); /* { dg-warning "into a region" } */ + T ( 2, "%c%c", '1', '2'); /* { dg-warning "nul past the end" } */ + T ( 3, "%c%c", '1', '2'); + + T ( 2, "%1$c%2$c", '1', '2'); /* { dg-warning "does not support %n.|nul past the end" } */ + T ( 3, "%1$c%2$c", '1', '2'); + + /* Verify that a warning is issued for exceeding INT_MAX bytes and + not otherwise. */ + T (-1, "%*c", INT_MAX - 1, '1'); + T (-1, "%*c", INT_MAX, '1'); + T (-1, "X%*c", INT_MAX - 1, '1'); + T (-1, "X%*c", INT_MAX, '1'); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */ + + T (-1, "%*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 (-1, "%*cX", INT_MAX - 2, '1'); + T (-1, "%*cX", INT_MAX - 1, '1'); + T (-1, "%*cX", INT_MAX, '1'); /* { dg-warning "output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */ +} + +/* Exercise the "%p" directive with constant arguments. */ + +void test_sprintf_p_const (void) +{ + /* GLIBC and uClibc format null pointers as "(nil)". Sane implementations + format null pointers as 0 or 0x0 and so the following will only be + diagnosed on the former targets. */ + T (5, "%p", (void*)0); + /* { dg-warning "nul past the end" "(nil)" { target *-linux-gnu *-*-uclinux } 94 } */ + + /* The exact output for %p is unspecified by C. Two formats are known: + same as %tx (for example AIX) and same as %#tx (for example Solaris). */ + T (0, "%p", (void*)0x1); /* { dg-warning ".%p. directive writing . bytes into a region of size 0" } */ + T (1, "%p", (void*)0x12); /* { dg-warning ".%p. directive writing . bytes into a region of size 1" } */ + T (2, "%p", (void*)0x123); /* { dg-warning ".%p. directive writing . bytes into a region of size 2" } */ + + /* GLIBC and uClibc treat the ' ' flag with the "%p" directive the same + as with signed integer conversions (i.e., it prepends a space). Other + known implementations ignore it. */ + T (6, "% p", (void*)0x234); /* { dg-warning ". . flag used with .%p." } */ + /* { dg-warning "nul past the end" "Glibc %p" { target *-linux-gnu } 106 } */ + /* { dg-warning "nul past the end" "Generic %p" { target *-*-uclinux } 106 } */ +} + +/* Verify that no warning is issued for calls that write into a flexible + array member whose size isn't known. Also verify that calls that use + a flexible array member as an argument to the "%s" directive do not + cause a warning. */ + +void test_sprintf_flexarray (void *p, int i) +{ + struct S + { + int n; + char a []; + } *s = p; + + __builtin_sprintf (s->a, "%c", 'x'); + + __builtin_sprintf (s->a, "%s", ""); + __builtin_sprintf (s->a, "%s", "abc"); + __builtin_sprintf (s->a, "abc%sghi", "def"); + + __builtin_sprintf (s->a, "%i", 1234); + + __builtin_sprintf (buffer (1), "%s", s->a); + __builtin_sprintf (buffer (1), "%s", s [i].a); +} + +/* Same as above but for zero-length arrays. */ + +void test_sprintf_zero_length_array (void *p, int i) +{ + struct S + { + int n; + char a [0]; + } *s = p; + + __builtin_sprintf (s->a, "%c", 'x'); + + __builtin_sprintf (s->a, "%s", ""); + __builtin_sprintf (s->a, "%s", "abc"); + __builtin_sprintf (s->a, "abc%sghi", "def"); + + __builtin_sprintf (s->a, "%i", 1234); + + __builtin_sprintf (buffer (1), "%s", s->a); + __builtin_sprintf (buffer (1), "%s", s [i].a); +} + +/* Verify that the note printed along with the diagnostic mentions + the correct sizes and refers to the location corresponding to + the affected directive. */ + +void test_sprintf_note (void) +{ +#define P __builtin_sprintf + + /* Diagnostic column numbers are 1-based. */ + + P (buffer (0), /* { dg-message "format output 4 bytes into a destination of size 0" } */ + "%c%s%i", '1', "2", 3); /* { dg-warning "7:.%c. directive writing 1 byte into a region of size 0" } */ + + P (buffer (1), /* { dg-message "format output 6 bytes into a destination of size 1" } */ + "%c%s%i", '1', "23", 45); /* { dg-warning "9:.%s. directive writing 2 bytes into a region of size 0" } */ + + P (buffer (2), /* { dg-message "format output 6 bytes into a destination of size 2" } */ + "%c%s%i", '1', "2", 345); /* { dg-warning "11:.%i. directive writing 3 bytes into a region of size 0" } */ + + /* It would be nice if the caret in the location range for the format + string below could be made to point at the closing quote of the format + string, like so: + sprintf (d, "%c%s%i", '1', "2", 3456); + ~~~~~~^ + Unfortunately, that doesn't work with the current setup. */ + P (buffer (6), /* { dg-message "format output 7 bytes into a destination of size 6" } */ + "%c%s%i", '1', "2", 3456); /* { dg-warning "writing a terminating nul past the end of the destination" } */ +} + +#undef T +#define T(size, fmt, ...) \ + __builtin___sprintf_chk (buffer (size), 0, objsize (size), fmt, \ + __VA_ARGS__), sink (buffer, ptr) + +/* Exercise the "%c" and "%lc" directive with constant arguments. */ + +void test_sprintf_chk_c_const (void) +{ + T (-1, "%c", 0); /* No warning for unknown destination size. */ + /* Verify the full text of the diagnostic for just the distinct messages + and use abbreviations in subsequent test cases. */ + T (0, "%c", 0); /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */ + T (1, "%c", 0); /* { dg-warning "writing a terminating nul past the end" } */ + T (1, "%c", '1'); /* { dg-warning "nul past the end" } */ + T (2, "%c", '1'); + T (2, "%2c", '1'); /* { dg-warning "nul past the end" } */ + T (2, "%3c", '1'); /* { dg-warning "into a region" } */ + T (2, "%c%c", '1', '2'); /* { dg-warning "nul past the end" } */ + T (3, "%c%c", '1', '2'); + + /* Wide characters. */ + T (0, "%lc", 0); /* { dg-warning "nul past the end" } */ + T (1, "%lc", 0); + T (1, "%lc%lc", 0, 0); + T (2, "%lc", 0); + T (2, "%lc%lc", 0, 0); + + /* The following could result in as few as no bytes and in as many as + MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property + the write cannot be reliably diagnosed. */ + T (2, "%lc", L'1'); + T (2, "%1lc", L'1'); + /* Writing some unknown number of bytes into a field two characters wide. */ + T (2, "%2lc", L'1'); /* { dg-warning "nul past the end" } */ + + T (3, "%lc%c", L'1', '2'); + /* Here in the best case each argument will format as single character, + causing the terminating NUL to be written past the end. */ + T (3, "%lc%c%c", L'1', '2', '3'); /* { dg-warning "nul past the end" } */ + T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "nul past the end" } */ +} + +/* Exercise the "%s" and "%ls" directive with constant arguments. */ + +void test_sprintf_chk_s_const (void) +{ + T (-1, "%*s", 0, ""); /* No warning for unknown destination size. */ + T ( 0, "%*s", 0, ""); /* { dg-warning "nul past the end" } */ + T ( 0, "%-s", ""); /* { dg-warning "nul past the end" } */ + T ( 0, "%*s", 0, s0); /* { dg-warning "nul past the end" } */ + T ( 1, "%*s", 0, ""); + T ( 1, "%*s", 0, s0); + T ( 1, "%*s", 0, "\0"); + T ( 1, "%*s", 0, "1"); /* { dg-warning "nul past the end" } */ + T ( 1, "%*s", 0, s1); /* { dg-warning "nul past the end" } */ + T ( 1, "%1s", ""); /* { dg-warning "nul past the end" } */ + T ( 1, "%1s", s0); /* { dg-warning "nul past the end" } */ + T (-1, "%1s", "1"); /* No warning for unknown destination size. */ + T ( 1, "%*s", 1, ""); /* { dg-warning "nul past the end" } */ + T ( 1, "%*s", 1, s0); /* { dg-warning "nul past the end" } */ + T (-1, "%*s", 1, s0); /* No warning for unknown destination size. */ + + T (1, "%.0s", "123"); + T (1, "%.0s", s3); + T (1, "%.*s", 0, "123"); + T (1, "%.*s", 0, s3); + T (1, "%.1s", "123"); /* { dg-warning "nul past the end" } */ + T (1, "%.1s", s3); /* { dg-warning "nul past the end" } */ + T (1, "%.*s", 1, "123"); /* { dg-warning "nul past the end" } */ + T (1, "%.*s", 1, s3); /* { dg-warning "nul past the end" } */ + + T (2, "%.*s", 0, ""); + T (2, "%.*s", 0, "1"); + T (2, "%.*s", 0, s1); + T (2, "%.*s", 0, "1\0"); + T (2, "%.*s", 0, "12"); + T (2, "%.*s", 0, s2); + + T (2, "%.*s", 1, ""); + T (2, "%.*s", 1, "1"); + T (2, "%.*s", 1, s1); + T (2, "%.*s", 1, "1\0"); + T (2, "%.*s", 1, "12"); + T (2, "%.*s", 1, s2); + + T (2, "%.*s", 2, ""); + T (2, "%.*s", 2, "1"); + T (2, "%.*s", 2, s1); + T (2, "%.*s", 2, "1\0"); + T (2, "%.*s", 2, "12"); /* { dg-warning "nul past the end" } */ + T (2, "%.*s", 2, s2); /* { dg-warning "nul past the end" } */ + + T (2, "%.*s", 3, ""); + T (2, "%.*s", 3, "1"); + T (2, "%.*s", 3, s1); + T (2, "%.*s", 3, "1\0"); + T (2, "%.*s", 3, "12"); /* { dg-warning "nul past the end" } */ + T (2, "%.*s", 3, "123"); /* { dg-warning "into a region" } */ + T (2, "%.*s", 3, s2); /* { dg-warning "nul past the end" } */ + T (2, "%.*s", 3, s3); /* { dg-warning "into a region" } */ + + T (2, "%*s", 0, ""); + T (2, "%*s", 0, "1"); + T (2, "%*s", 0, s1); + T (2, "%*s", 0, "1\0"); + T (2, "%*s", 0, "12"); /* { dg-warning "nul past the end" } */ + T (2, "%*s", 0, s2); /* { dg-warning "nul past the end" } */ + + /* Verify that output in excess of INT_MAX bytes is diagnosed even + when the size of the destination object is unknown. */ + T (-1, "%*s", INT_MAX - 1, ""); + T (-1, "%*s", INT_MAX, ""); + T (-1, "X%*s", INT_MAX, ""); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */ + + /* Multiple directives. */ + + T (1, "%s%s", "", ""); + T (1, "%s%s", s0, s0); + T (1, "%s%s", "", "1"); /* { dg-warning "nul past the end" } */ + T (1, "%s%s", s0, s1); /* { dg-warning "nul past the end" } */ + T (1, "%s%s", "1", ""); /* { dg-warning "nul past the end" } */ + T (1, "%s%s", s1, s0); /* { dg-warning "nul past the end" } */ + T (1, "%s%s", "1", "2"); /* { dg-warning "into a region" } */ + T (1, "%s%s", s1, s1); /* { dg-warning "into a region" } */ + + T (2, "%s%s", "", ""); + T (2, "%s%s", "", "1"); + T (2, "%s%s", "1", ""); + T (2, "%s%s", "", "12"); /* { dg-warning "nul past the end" } */ + T (2, "%s%s", "1", "2"); /* { dg-warning "nul past the end" } */ + T (2, "%s%s", "12", "2"); /* { dg-warning "into a region" } */ + T (2, "%s%s", "1", "23"); /* { dg-warning "into a region" } */ + T (2, "%s%s", "12", "3"); /* { dg-warning "into a region" } */ + T (2, "%s%s", "12", "34"); /* { dg-warning "into a region" } */ + + T (2, "_%s", ""); + T (2, "%%%s", ""); + T (2, "%s%%", ""); + T (2, "_%s", "1"); /* { dg-warning "nul past the end" } */ + T (2, "%%%s", "1"); /* { dg-warning "nul past the end" } */ + T (2, "%s%%", "1"); /* { dg-warning "nul past the end" } */ + T (2, "_%s", "12"); /* { dg-warning "into a region" } */ + T (2, "__%s", "1"); /* { dg-warning "into a region" } */ + + T (2, "%1$s%2$s", "12", "3"); /* { dg-warning ".%2.s. directive writing 1 byte into a region of size 0" } */ + T (2, "%1$s%1$s", "12"); /* { dg-warning "does not support|.%1.s. directive writing 2 bytes into a region of size 0" } */ + T (2, "%2$s%1$s", "1", "23"); /* { dg-warning ".%1.s. directive writing 1 byte into a region of size 0" } */ + T (2, "%2$s%2$s", "1", "23"); /* { dg-warning "unused|%2.s. directive writing 2 bytes into a region of size 0" } */ + + T (3, "__%s", ""); + T (3, "__%s", "1"); /* { dg-warning "nul past the end" } */ + T (3, "%s_%s", "", ""); + T (3, "%s_%s", "1", ""); + T (3, "%s_%s", "", "1"); + T (3, "%s_%s", "1", "2"); /* { dg-warning "nul past the end" } */ + + /* Wide strings. */ + T (-1, "%ls", L""); + T ( 0, "%ls", L""); /* { dg-warning "nul past the end" } */ + T ( 1, "%ls", L""); + T ( 1, "%ls", L"\0"); + T ( 1, "%1ls", L""); /* { dg-warning "nul past the end" } */ + + T (0, "%*ls", 0, L""); /* { dg-warning "nul past the end" } */ + T (1, "%*ls", 0, L""); + T (1, "%*ls", 0, L"\0"); + T (1, "%*ls", 1, L""); /* { dg-warning "nul past the end" } */ + + T (1, "%ls", L"1"); /* { dg-warning "nul past the end" } */ + T (1, "%.0ls", L"1"); + T (2, "%.0ls", L"1"); + T (2, "%.1ls", L"1"); + T (2, "%.*ls", 1, L"1"); + + /* The "%.2ls" directive below will write at a minimum 1 byte (because + L"1" is known and can be assumed to convert to at least one multibyte + character), and at most 2 bytes because of the precision. Since its + output is explicitly bounded it is diagnosed. */ + T (2, "%.2ls", L"1"); /* { dg-warning "nul past the end" } */ + T (2, "%.*ls", 2, L"1"); /* { dg-warning "nul past the end" } */ + + T (3, "%.0ls", L"1"); + T (3, "%.1ls", L"1"); + T (3, "%.2ls", L"1"); +} + +/* Exercise the "%hhd", "%hhi", "%hho", "%hhu", and "%hhx" directives + with constant arguments. */ + +void test_sprintf_chk_hh_const (void) +{ + T (-1, "%hhd", 0); + + T (1, "%hhd", 0); /* { dg-warning "nul past the end" } */ + T (1, "%hhd", 1); /* { dg-warning "nul past the end" } */ + T (1, "%hhd", -1); /* { dg-warning "into a region" } */ + T (1, "%+hhd", 0); /* { dg-warning "into a region" } */ + T (1, "%+hhd", 1); /* { dg-warning "into a region" } */ + T (1, "%-hhd", 0); /* { dg-warning "nul past the end" } */ + + T (1, "%hhi", 0); /* { dg-warning "nul past the end" } */ + T (1, "%hhi", 1); /* { dg-warning "nul past the end" } */ + T (1, "%hhi", -1); /* { dg-warning "into a region" } */ + T (1, "%+hhi", 0); /* { dg-warning "into a region" } */ + T (1, "%+hhi", 1); /* { dg-warning "into a region" } */ + T (1, "%-hhi", 0); /* { dg-warning "nul past the end" } */ + + T (2, "%hhi", 0); + T (2, "%hhi", 1); + T (2, "%hhi", 9); + T (2, "% hhi", 9); /* { dg-warning "nul past the end" } */ + T (2, "%+hhi", 9); /* { dg-warning "nul past the end" } */ + T (2, "%-hhi", 9); + T (2, "%hhi", 10); /* { dg-warning "nul past the end" } */ + T (2, "%hhi", -1); /* { dg-warning "nul past the end" } */ + T (2, "% hhi", -1); /* { dg-warning "nul past the end" } */ + T (2, "%+hhi", -1); /* { dg-warning "nul past the end" } */ + T (2, "%-hhi", -1); /* { dg-warning "nul past the end" } */ + + T (2, "%hho", 0); + T (2, "%hho", 1); + T (2, "%hho", 7); + T (2, "%hho", 010); /* { dg-warning "nul past the end" } */ + T (2, "%hho", 077); /* { dg-warning "nul past the end" } */ + T (2, "%hho", -1); /* { dg-warning "into a region" } */ + + T (2, "%hhx", 0); + T (2, "%hhX", 1); + T (2, "%hhx", 7); + T (2, "%hhX", 8); + T (2, "%hhx", -1); /* { dg-warning "nul past the end" } */ + T (2, "%hhX", 0xf); + T (2, "%hhx", 0x10); /* { dg-warning "nul past the end" } */ + T (2, "%hhX", 0xff); /* { dg-warning "nul past the end" } */ + + T (1, "%#hhx", 0); /* { dg-warning "nul past the end" } */ + T (2, "%#hhx", 0); + T (3, "%#hhx", 1); /* { dg-warning "nul past the end" } */ + + T (4, "%hhd", 255); + T (4, "%hhd", 256); + T (4, "%hhd", 0xfff); + T (4, "%hhd", 0xffff); + + T (4, "%hhi", 255); + T (4, "%hhi", 256); + T (4, "%hhi", 0xfff); + T (4, "%hhi", 0xffff); + + T (4, "%hhu", -1); + T (4, "%hhu", 255); + T (4, "%hhu", 256); + T (4, "%hhu", 0xfff); + T (4, "%hhu", 0xffff); + + T (4, "%#hhx", 0); + T (4, "%#hhx", 1); + T (4, "%#hhx", -1); /* { dg-warning "nul past the end" } */ + T (4, "%#hhx", 0xf); + T (4, "%#hhx", 0x10); /* { dg-warning "nul past the end" } */ + T (4, "%#hhx", 0xff); /* { dg-warning "nul past the end" } */ + T (4, "%#hhx", 0xfff); /* { dg-warning "nul past the end" } */ + + T (4, "%hhi %hhi", 0, 0); + T (4, "%hhi %hhi", 9, 9); + T (4, "%hhi %hhi", 1, 10); /* { dg-warning "nul past the end" } */ + T (4, "%hhi %hhi", 10, 1); /* { dg-warning "nul past the end" } */ + T (4, "%hhi %hhi", 11, 12); /* { dg-warning "into a region" } */ + + T (5, "%0*hhd %0*hhi", 0, 7, 0, 9); + T (5, "%0*hhd %0*hhi", 1, 7, 1, 9); + T (5, "%0*hhd %0*hhi", 1, 7, 2, 9); + T (5, "%0*hhd %0*hhi", 2, 7, 1, 9); + T (5, "%0*hhd %0*hhi", 2, 7, 2, 9); /* { dg-warning "nul past the end" } */ + T (5, "%0*hhd %0*hhi", 0, 12, 0, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */ + T (5, "%0*hhd %0*hhi", 1, 12, 1, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */ + T (5, "%0*hhd %0*hhi", 2, 12, 3, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */ + + /* FIXME: Move the boundary test cases into a file of their own that's + exercised only on targets with the matching type limits (otherwise + they'll fail). */ +#undef MAX +#define MAX 127 + +#undef MIN +#define MIN (-MAX -1) + + T (1, "%hhi", MAX); /* { dg-warning "into a region" } */ + T (1, "%hhi", MIN); /* { dg-warning "into a region" } */ + T (1, "%hhi", MAX + 1); /* { dg-warning "into a region" } */ + + T (2, "%hhi", MAX + 1); /* { dg-warning "into a region" } */ + T (2, "%hhi", MAX + 10); /* { dg-warning "into a region" } */ + T (2, "%hhi", MAX + 100); /* { dg-warning "into a region" } */ +} + +/* Exercise the "%hhd", "%hi", "%ho", "%hu", and "%hx" directives + with constant arguments. */ + +void test_sprintf_chk_h_const (void) +{ + T (1, "%hu", 0); /* { dg-warning "nul past the end" } */ + T (1, "%hu", 1); /* { dg-warning "nul past the end" } */ + T (1, "%hu", -1); /* { dg-warning "into a region" } */ + + T (2, "%hi", 0); + T (2, "%hi", 1); + T (2, "%hi", 9); + T (2, "% hi", 9); /* { dg-warning "nul past the end" } */ + T (2, "%+hi", 9); /* { dg-warning "nul past the end" } */ + T (2, "%-hi", 9); + T (2, "%hi", 10); /* { dg-warning "nul past the end" } */ + T (2, "%hi", -1); /* { dg-warning "nul past the end" } */ + T (2, "% hi", -2); /* { dg-warning "nul past the end" } */ + T (2, "%+hi", -3); /* { dg-warning "nul past the end" } */ + T (2, "%-hi", -4); /* { dg-warning "nul past the end" } */ + + T (2, "%hu", 0); + T (2, "%hu", 1); + T (2, "%hu", 9); + T (2, "%hu", 10); /* { dg-warning "nul past the end" } */ + T (2, "%hu", -1); /* { dg-warning "into a region" } */ + + T (2, "%ho", 0); + T (2, "%ho", 1); + T (2, "%ho", 7); + T (2, "%ho", 010); /* { dg-warning "nul past the end" } */ + T (2, "%ho", 077); /* { dg-warning "nul past the end" } */ + T (2, "%ho", 0100); /* { dg-warning "into a region" } */ + T (2, "%ho", -1); /* { dg-warning "into a region" } */ + + T (2, "%hx", 0); + T (2, "%hx", 1); + T (2, "%hx", 7); + T (2, "%hx", 0xf); + T (2, "%hx", 0x10); /* { dg-warning "nul past the end" } */ + T (2, "%hx", 0xff); /* { dg-warning "nul past the end" } */ + T (2, "%hx", 0x100); /* { dg-warning "into a region" } */ + T (2, "%hx", -1); /* { dg-warning "into a region" } */ + + T (3, "% hi", 7); + T (3, "%+hi", 8); + T (3, "%-hi", 9); + T (3, "%hi", 10); + T (3, "%hi", -1); + T (3, "% hi", -2); + T (3, "%+hi", -3); + T (3, "%-hi", -4); + + T (5, "%hu", 9999); + T (5, "%hu", 10000); /* { dg-warning "nul past the end" } */ + T (5, "%hu", 65535); /* { dg-warning "nul past the end" } */ + + T (1, "%#hx", 0); /* { dg-warning "nul past the end" } */ + T (2, "%#hx", 0); + T (3, "%#hx", 1); /* { dg-warning "nul past the end" } */ + + T (4, "%#hx", 0); + T (4, "%#hx", 1); + T (4, "%#hx", 0xf); + T (4, "%#hx", 0x10); /* { dg-warning "nul past the end" } */ + T (4, "%#hx", 0xff); /* { dg-warning "nul past the end" } */ + T (4, "%#hx", 0x100); /* { dg-warning "into a region" } */ + T (4, "%#hx", -1); /* { dg-warning "into a region" } */ + +#undef MAX +#define MAX 65535 + + T (1, "%hhu", 0); /* { dg-warning "nul past the end" } */ + T (1, "%hhu", 1); /* { dg-warning "nul past the end" } */ + T (1, "%hhu", -1); /* { dg-warning "into a region" } */ + T (1, "%hhu", MAX); /* { dg-warning "into a region" } */ + T (1, "%hhu", MAX + 1); /* { dg-warning "nul past the end" } */ +} + +/* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with + constant arguments. */ + +void test_sprintf_chk_integer_const (void) +{ + T ( 1, "%i", 0); /* { dg-warning "nul past the end" } */ + T ( 1, "%i", 1); /* { dg-warning "nul past the end" } */ + T ( 1, "%i", -1); /* { dg-warning "into a region" } */ + T ( 1, "%i_", 1); /* { dg-warning "character ._. at offset 2 past the end" } */ + T ( 1, "_%i", 1); /* { dg-warning "into a region" } */ + T ( 1, "_%i_", 1); /* { dg-warning "into a region" } */ + T ( 1, "%o", 0); /* { dg-warning "nul past the end" } */ + T ( 1, "%u", 0); /* { dg-warning "nul past the end" } */ + T ( 1, "%x", 0); /* { dg-warning "nul past the end" } */ + T ( 1, "%#x", 0); /* { dg-warning "nul past the end" } */ + T ( 1, "%x", 1); /* { dg-warning "nul past the end" } */ + T ( 1, "%#x", 1); /* { dg-warning "into a region" } */ + + T ( 2, "%i", 0); + T ( 2, "%i", 1); + T ( 2, "%i", 9); + T ( 2, "%i", -1); /* { dg-warning "nul past the end" } */ + T ( 2, "%i", 10); /* { dg-warning "nul past the end" } */ + T ( 2, "%i_", 0); /* { dg-warning "nul past the end" } */ + T ( 2, "_%i", 0); /* { dg-warning "nul past the end" } */ + T ( 2, "_%i_", 0); /* { dg-warning "character ._. at offset 3 past the end" } */ + T ( 2, "%o", 1); + T ( 2, "%o", 7); + T ( 2, "%o", 010); /* { dg-warning "nul past the end" } */ + T ( 2, "%o", 0100); /* { dg-warning "into a region" } */ + T ( 2, "%x", 1); + T ( 2, "%#x", 1); /* { dg-warning "into a region" } */ + T ( 2, "%x", 0xa); + T ( 2, "%x", 0xf); + T ( 2, "%x", 0x10); /* { dg-warning "nul past the end" } */ + T ( 2, "%x", 0xff); /* { dg-warning "nul past the end" } */ + T ( 2, "%x", 0x1ff); /* { dg-warning "into a region" } */ + + T ( 3, "%i", 0); + T ( 3, "%i", 1); + T ( 3, "%i", 9); + T ( 3, "%i", -9); + T ( 3, "%i", 10); + T ( 3, "%i", 99); + T ( 3, "%i", -99); /* { dg-warning "nul past the end" } */ + + /* ~0U is formatted into exactly three bytes as "-1" followed by + the terminating NUL character. */ + T ( 3, "%+i", ~0U); + T ( 3, "%-i", ~0U); + T ( 3, "% i", ~0U); + + T ( 8, "%8u", 1); /* { dg-warning "nul past the end" } */ + T ( 9, "%8u", 1); + + T ( 7, "%1$i%2$i%3$i", 1, 23, 456); + T ( 8, "%1$i%2$i%3$i%1$i", 1, 23, 456); + T ( 8, "%1$i%2$i%3$i%2$i", 1, 23, 456); /* { dg-warning "nul past the end" } */ + T ( 8, "%1$i%2$i%3$i%3$i", 1, 23, 456); /* { dg-warning "into a region" } */ + +#undef MAX +#define MAX 2147483647 /* 10 digits. */ +#undef MIN +#define MIN (-MAX -1) /* Sign plus 10 digits. */ + + T ( 1, "%i", MAX); /* { dg-warning "into a region" } */ + T ( 1, "%i", MIN); /* { dg-warning "into a region" } */ + T ( 2, "%i", MAX); /* { dg-warning "into a region" } */ + T ( 2, "%i", MIN); /* { dg-warning "into a region" } */ + T (10, "%i", 123456789); + T (10, "%i", -123456789); /* { dg-warning "nul past the end" } */ + T (10, "%i", MAX); /* { dg-warning "nul past the end" } */ + T (10, "%i", MIN); /* { dg-warning "into a region" } */ + + T (11, "%i", MAX); + T (11, "%i", MIN); /* { dg-warning "nul past the end" } */ +} + +/* Exercise the "%jd", "%ji", "%jo", "%ju", and "%jx" directives + for the formatting of intmax_t and uintmax_t values with constant + arguments. */ + +void test_sprintf_chk_j_const (void) +{ +#define I(x) ((__INTMAX_TYPE__)x) + + T ( 1, "%ji", I ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%ji", I ( 1)); /* { dg-warning "nul past the end" } */ + T ( 1, "%ji", I ( -1)); /* { dg-warning "into a region" } */ + T ( 1, "%ji_", I ( 1)); /* { dg-warning "character ._. at offset 3 past the end" } */ + T ( 1, "_%ji", I ( 1)); /* { dg-warning "into a region" } */ + T ( 1, "_%ji_",I ( 1)); /* { dg-warning "into a region" } */ + T ( 1, "%jo", I ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%ju", I ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%jx", I ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%#jx", I ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%jx", I ( 1)); /* { dg-warning "nul past the end" } */ + T ( 1, "%#jx", I ( 1)); /* { dg-warning "into a region" } */ + + T ( 2, "%ji", I ( 0)); + T ( 2, "%ji", I ( 1)); + T ( 2, "%ji", I ( 9)); + T ( 2, "%ji", I ( -1)); /* { dg-warning "nul past the end" } */ + T ( 2, "%ji", I ( 10)); /* { dg-warning "nul past the end" } */ + T ( 2, "%ji_", I ( 0)); /* { dg-warning "nul past the end" } */ + T ( 2, "_%ji", I ( 0)); /* { dg-warning "nul past the end" } */ + T ( 2, "_%ji_",I ( 0)); /* { dg-warning "character ._. at offset 4 past the end" } */ + T ( 2, "%jo", I ( 1)); + T ( 2, "%jo", I ( 7)); + T ( 2, "%jo", I ( 010)); /* { dg-warning "nul past the end" } */ + T ( 2, "%jo", I ( 0100)); /* { dg-warning "into a region" } */ + T ( 2, "%jx", I ( 1)); + T ( 2, "%#jx", I ( 1)); /* { dg-warning "into a region" } */ + T ( 2, "%jx", I ( 0xa)); + T ( 2, "%jx", I ( 0xf)); + T ( 2, "%jx", I ( 0x10)); /* { dg-warning "nul past the end" } */ + T ( 2, "%jx", I ( 0xff)); /* { dg-warning "nul past the end" } */ + T ( 2, "%jx", I (0x1ff)); /* { dg-warning "into a region" } */ + + T ( 3, "%ji", I ( 0)); + T ( 3, "%ji", I ( 1)); + T ( 3, "%ji", I ( 9)); + T ( 3, "%ji", I ( -9)); + T ( 3, "%ji", I ( 10)); + T ( 3, "%ji", I ( 99)); + T ( 3, "%ji", I ( -99)); /* { dg-warning "nul past the end" } */ + + /* ~0 is formatted into exactly three bytes as "-1" followed by + the terminating NUL character. */ + T ( 3, "%+ji", ~I (0)); + T ( 3, "%-ji", ~I (0)); + T ( 3, "% ji", ~I (0)); + + T ( 8, "%8ju", I (1)); /* { dg-warning "nul past the end" } */ + T ( 9, "%8ju", I (1)); +} + +/* Exercise the "%ld", "%li", "%lo", "%lu", and "%lx" directives + with constant arguments. */ + +void test_sprintf_chk_l_const (void) +{ + T ( 1, "%li", 0L); /* { dg-warning "nul past the end" } */ + T ( 1, "%li", 1L); /* { dg-warning "nul past the end" } */ + T ( 1, "%li", -1L); /* { dg-warning "into a region" } */ + T ( 1, "%li_", 1L); /* { dg-warning "character ._. at offset 3 past the end" } */ + T ( 1, "_%li", 1L); /* { dg-warning "into a region" } */ + T ( 1, "_%li_", 1L); /* { dg-warning "into a region" } */ + T ( 1, "%lo", 0L); /* { dg-warning "nul past the end" } */ + T ( 1, "%lu", 0L); /* { dg-warning "nul past the end" } */ + T ( 1, "%lx", 0L); /* { dg-warning "nul past the end" } */ + T ( 1, "%#lx", 0L); /* { dg-warning "nul past the end" } */ + T ( 1, "%lx", 1L); /* { dg-warning "nul past the end" } */ + T ( 1, "%#lx", 1L); /* { dg-warning "into a region" } */ + + T ( 2, "%li", 0L); + T ( 2, "%li", 1L); + T ( 2, "%li", 9L); + T ( 2, "%li", -1L); /* { dg-warning "nul past the end" } */ + T ( 2, "%li", 10L); /* { dg-warning "nul past the end" } */ + T ( 2, "%li_", 0L); /* { dg-warning "nul past the end" } */ + T ( 2, "_%li", 0L); /* { dg-warning "nul past the end" } */ + T ( 2, "_%li_", 0L); /* { dg-warning "character ._. at offset 4 past the end" } */ + T ( 2, "%lo", 1L); + T ( 2, "%lo", 7L); + T ( 2, "%lo", 010L); /* { dg-warning "nul past the end" } */ + T ( 2, "%lo", 0100L); /* { dg-warning "into a region" } */ + T ( 2, "%lx", 1L); + T ( 2, "%#lx", 1L); /* { dg-warning "into a region" } */ + T ( 2, "%lx", 0xaL); + T ( 2, "%lx", 0xfL); + T ( 2, "%lx", 0x10L); /* { dg-warning "nul past the end" } */ + T ( 2, "%lx", 0xffL); /* { dg-warning "nul past the end" } */ + T ( 2, "%lx", 0x1ffL); /* { dg-warning "into a region" } */ + + T ( 3, "%li", 0L); + T ( 3, "%li", 1L); + T ( 3, "%li", 9L); + T ( 3, "%li", -9L); + T ( 3, "%li", 10L); + T ( 3, "%li", 99L); + T ( 3, "%li", -99L); /* { dg-warning "nul past the end" } */ + + /* ~0U is formatted into exactly three bytes as "-1" followed by + the terminating NUL character. */ + T ( 3, "%+li", ~0LU); + T ( 3, "%-li", ~0LU); + T ( 3, "% li", ~0LU); + + T ( 8, "%8lu", 1L); /* { dg-warning "nul past the end" } */ + T ( 9, "%8lu", 1L); +} + +/* Exercise the "%lld", "%lli", "%llo", "%llu", and "%llx" directives + with constant arguments. */ + +void test_sprintf_chk_ll_const (void) +{ + T ( 1, "%lli", 0LL); /* { dg-warning "nul past the end" } */ + T ( 1, "%lli", 1LL); /* { dg-warning "nul past the end" } */ + T ( 1, "%lli", -1LL); /* { dg-warning "into a region" } */ + T ( 1, "%lli_", 1LL); /* { dg-warning "character ._. at offset 4 past the end" } */ + T ( 1, "_%lli", 1LL); /* { dg-warning "into a region" } */ + T ( 1, "_%lli_", 1LL); /* { dg-warning "into a region" } */ + T ( 1, "%llo", 0LL); /* { dg-warning "nul past the end" } */ + T ( 1, "%llu", 0LL); /* { dg-warning "nul past the end" } */ + T ( 1, "%llx", 0LL); /* { dg-warning "nul past the end" } */ + T ( 1, "%#llx", 0LL); /* { dg-warning "nul past the end" } */ + T ( 1, "%llx", 1LL); /* { dg-warning "nul past the end" } */ + T ( 1, "%#llx", 1LL); /* { dg-warning "into a region" } */ + + T ( 2, "%lli", 0LL); + T ( 2, "%lli", 1LL); + T ( 2, "%lli", 9LL); + T ( 2, "%lli", -1LL); /* { dg-warning "nul past the end" } */ + T ( 2, "%lli", 10LL); /* { dg-warning "nul past the end" } */ + T ( 2, "%lli_", 0LL); /* { dg-warning "nul past the end" } */ + T ( 2, "_%lli", 0LL); /* { dg-warning "nul past the end" } */ + T ( 2, "_%lli_", 0LL); /* { dg-warning "character ._. at offset 5 past the end" } */ + T ( 2, "%llo", 1LL); + T ( 2, "%llo", 7LL); + T ( 2, "%llo", 010LL); /* { dg-warning "nul past the end" } */ + T ( 2, "%llo", 0100LL); /* { dg-warning "into a region" } */ + T ( 2, "%llx", 1LL); + T ( 2, "%#llx", 1LL); /* { dg-warning "into a region" } */ + T ( 2, "%llx", 0xaLL); + T ( 2, "%llx", 0xfLL); + T ( 2, "%llx", 0x10LL); /* { dg-warning "nul past the end" } */ + T ( 2, "%llx", 0xffLL); /* { dg-warning "nul past the end" } */ + T ( 2, "%llx", 0x1ffLL); /* { dg-warning "into a region" } */ + + T ( 3, "%lli", 0LL); + T ( 3, "%lli", 1LL); + T ( 3, "%lli", 9LL); + T ( 3, "%lli", -9LL); + T ( 3, "%lli", 10LL); + T ( 3, "%lli", 99LL); + T ( 3, "%lli", -99LL); /* { dg-warning "nul past the end" } */ + + /* ~0U is formatted into exactly three bytes as "-1" followed by + the terminating NUL character. */ + T ( 3, "%+lli", ~0LLU); + T ( 3, "%-lli", ~0LLU); + T ( 3, "% lli", ~0LLU); + + T ( 8, "%8llu", 1LL); /* { dg-warning "nul past the end" } */ + T ( 9, "%8llu", 1LL); + + /* assume 64-bit long long. */ +#define LLONG_MAX 9223372036854775807LL /* 19 bytes */ +#define LLONG_MIN (-LLONG_MAX - 1) /* 20 bytes */ + + T (18, "%lli", LLONG_MIN); /* { dg-warning "into a region" } */ + T (19, "%lli", LLONG_MIN); /* { dg-warning "into a region" } */ + T (20, "%lli", LLONG_MIN); /* { dg-warning "nul past the end" } */ + T (21, "%lli", LLONG_MIN); + + T (18, "%lli", LLONG_MAX); /* { dg-warning "into a region" } */ + T (19, "%lli", LLONG_MAX); /* { dg-warning "nul past the end" } */ + T (20, "%lli", LLONG_MAX); + + T (21, "%llo", -1LL); /* { dg-warning "into a region" } */ + T (22, "%llo", -1LL); /* { dg-warning "nul past the end" } */ + T (23, "%llo", -1LL); + + T (19, "%llu", -1LL); /* { dg-warning "into a region" } */ + T (20, "%llu", -1LL); /* { dg-warning "nul past the end" } */ + T (21, "%llu", -1LL); + + T (15, "%llx", -1LL); /* { dg-warning "into a region" } */ + T (16, "%llx", -1LL); /* { dg-warning "nul past the end" } */ + T (17, "%llx", -1LL); +} + +void test_sprintf_chk_L_const (void) +{ + T (-1, "%Li", 0LL); + T ( 1, "%Li", 0LL); /* { dg-warning "nul past the end" } */ + T ( 1, "%Li", 1LL); /* { dg-warning "nul past the end" } */ + T ( 1, "%Li", -1LL); /* { dg-warning "into a region" } */ + T ( 1, "%Li_", 1LL); /* { dg-warning "character ._. at offset 3 past the end" } */ + T ( 1, "_%Li", 1LL); /* { dg-warning "into a region" } */ + T ( 1, "_%Li_", 1LL); /* { dg-warning "into a region" } */ +} + +void test_sprintf_chk_z_const (void) +{ + T (-1, "%zi", (size_t)0); + T ( 1, "%zi", (size_t)0); /* { dg-warning "nul past the end" } */ + T ( 1, "%zi", (size_t)1); /* { dg-warning "nul past the end" } */ + T ( 1, "%zi", (size_t)-1L);/* { dg-warning "into a region" } */ + T ( 1, "%zi_", (size_t)1); /* { dg-warning "character ._. at offset 3 past the end" } */ + T ( 1, "_%zi", (size_t)1); /* { dg-warning "into a region" } */ + T ( 1, "_%zi_", (size_t)1); /* { dg-warning "into a region" } */ + + T ( 2, "%zu", (size_t)1); + T ( 2, "%zu", (size_t)9); + T ( 2, "%zu", (size_t)10); /* { dg-warning "nul past the end" } */ +} + +void test_sprintf_chk_e_const (void) +{ + T (-1, "%E", 0.0); + T ( 0, "%E", 0.0); /* { dg-warning "into a region" } */ + T ( 0, "%e", 0.0); /* { dg-warning "into a region" } */ + T ( 1, "%E", 1.0); /* { dg-warning "into a region" } */ + T ( 1, "%e", 1.0); /* { dg-warning "into a region" } */ + T ( 2, "%e", 2.0); /* { dg-warning "into a region" } */ + T ( 3, "%e", 3.0); /* { dg-warning "into a region" } */ + T (12, "%e", 1.2); /* { dg-warning "nul past the end" } */ + T (12, "%e", 12.0); /* { dg-warning "nul past the end" } */ + T (13, "%e", 1.3); /* 1.300000e+00 */ + T (13, "%E", 13.0); /* 1.300000e+01 */ + T (13, "%e", 13.0); + T (13, "%E", 1.4e+99); /* 1.400000e+99 */ + T (13, "%e", 1.5e+100); /* { dg-warning "nul past the end" } */ + T (14, "%E", 1.6e+101); /* 1.600000E+101 */ + T (14, "%e", -1.7e+102); /* { dg-warning "nul past the end" } */ + T (15, "%E", -1.8e+103); /* -1.800000E+103 */ + + T (16, "%.8e", -1.9e+104); /* { dg-warning "nul past the end" } */ + T (17, "%.8e", -2.0e+105); /* -2.00000000e+105 */ + + T ( 5, "%.0e", 0.0); /* { dg-warning "nul past the end" } */ + T ( 5, "%.0e", 1.0); /* { dg-warning "nul past the end" } */ + T ( 6, "%.0e", 1.0); + + /* The actual output of the following directives depends on the rounding + mode. Verify that the warning correctly reflects that. */ + T (12, "%e", 9.999999e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%e", 9.9999994e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%e", 9.9999995e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%e", 9.9999996e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%e", 9.9999997e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%e", 9.9999998e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ + + T (12, "%Le", 9.9999994e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%Le", 9.9999995e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%Le", 9.9999996e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%Le", 9.9999997e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%Le", 9.9999998e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ + T (12, "%Le", 9.9999999e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ +} + +/* At -Wformat-length level 1 unknown numbers are assumed to have + the value one, and unknown strings are assumed to have a zero + length. */ + +void test_sprintf_chk_s_nonconst (int i, const char *s) +{ + T (-1, "%s", s); + T ( 0, "%s", s); /* { dg-warning "nul past the end" } */ + T ( 1, "%s", s); + T ( 1, "%.0s", s); + T ( 1, "%.1s", s); /* { dg-warning "nul past the end" } */ + + /* The following will definitely write past the end of the buffer, + but since at level 1 the length of an unknown string argument + is assumed to be zero, it will write the terminating nul past + the end (we don't print "past the end" when we're not + sure which we can't be with an unknown string. */ + T (1, "%1s", s); /* { dg-warning "writing a terminating nul past the end" } */ +} + +/* Exercise the hh length modifier with all integer specifiers and + a non-constant argument. */ + +void test_sprintf_chk_hh_nonconst (int a) +{ + T (-1, "%hhd", a); + + T (0, "%hhd", a); /* { dg-warning "into a region" } */ + T (0, "%hhi", a); /* { dg-warning "into a region" } */ + T (0, "%hhu", a); /* { dg-warning "into a region" } */ + T (0, "%hhx", a); /* { dg-warning "into a region" } */ + + T (1, "%hhd", a); /* { dg-warning "nul past the end" } */ + T (1, "%hhi", a); /* { dg-warning "nul past the end" } */ + T (1, "%hhu", a); /* { dg-warning "nul past the end" } */ + T (1, "%hhx", a); /* { dg-warning "nul past the end" } */ + + T (1, "% hhd", a); /* { dg-warning "into a region" } */ + T (1, "% hhi", a); /* { dg-warning "into a region" } */ + T (1, "%+hhd", a); /* { dg-warning "into a region" } */ + T (1, "%+hhi", a); /* { dg-warning "into a region" } */ + T (1, "%-hhd", a); /* { dg-warning "nul past the end" } */ + T (1, "%-hhi", a); /* { dg-warning "nul past the end" } */ + + T (2, "%hhd", a); + T (2, "%hhi", a); + T (2, "%hho", a); + T (2, "%hhu", a); + T (2, "%hhx", a); + + T (2, "% hhd", a); /* { dg-warning "nul past the end" } */ + T (2, "% hhi", a); /* { dg-warning "nul past the end" } */ + T (2, "% hho", a); /* { dg-warning ". . flag used with .%o." } */ + T (2, "% hhu", a); /* { dg-warning ". . flag used with .%u." } */ + T (2, "% hhx", a); /* { dg-warning ". . flag used with .%x." } */ + + T (2, "#%hho", a); /* { dg-warning "nul past the end" } */ + T (2, "#%hhx", a); /* { dg-warning "nul past the end" } */ + + T (3, "%2hhd", a); + T (3, "%2hhi", a); + T (3, "%2hho", a); + T (3, "%2hhu", a); + T (3, "%2hhx", a); + + /* Exercise cases where the type of the actual argument (whose value + and range are unknown) constrain the size of the output and so + can be used to avoid what would otherwise be false positives. */ + + T (2, "%hhd", (UChar)a); + T (2, "%hhi", (UChar)a); + T (2, "%-hhi", (UChar)a); +} + +/* Exercise the h length modifier with all integer specifiers and + a non-constant argument. */ + +void test_sprintf_chk_h_nonconst (int a) +{ + T (-1, "%hd", a); + + T (0, "%hd", a); /* { dg-warning "into a region" } */ + T (0, "%hi", a); /* { dg-warning "into a region" } */ + T (0, "%hu", a); /* { dg-warning "into a region" } */ + T (0, "%hx", a); /* { dg-warning "into a region" } */ + + T (1, "%hd", a); /* { dg-warning "nul past the end" } */ + T (1, "%hi", a); /* { dg-warning "nul past the end" } */ + T (1, "%hu", a); /* { dg-warning "nul past the end" } */ + T (1, "%hx", a); /* { dg-warning "nul past the end" } */ + + T (1, "% hd", a); /* { dg-warning "into a region" } */ + T (1, "% hi", a); /* { dg-warning "into a region" } */ + T (1, "%+hd", a); /* { dg-warning "into a region" } */ + T (1, "%+hi", a); /* { dg-warning "into a region" } */ + T (1, "%-hd", a); /* { dg-warning "nul past the end" } */ + T (1, "%-hi", a); /* { dg-warning "nul past the end" } */ + + T (2, "%hd", a); + T (2, "%hi", a); + T (2, "%ho", a); + T (2, "%hu", a); + T (2, "%hx", a); + + T (2, "% hd", a); /* { dg-warning "nul past the end" } */ + T (2, "% hi", a); /* { dg-warning "nul past the end" } */ + T (2, "% ho", a); /* { dg-warning ". . flag used with .%o." } */ + T (2, "% hu", a); /* { dg-warning ". . flag used with .%u." } */ + T (2, "% hx", a); /* { dg-warning ". . flag used with .%x." } */ + + T (2, "#%ho", a); /* { dg-warning "nul past the end" } */ + T (2, "#%hx", a); /* { dg-warning "nul past the end" } */ + + T (3, "%2hd", a); + T (3, "%2hi", a); + T (3, "%2ho", a); + T (3, "%2hu", a); + T (3, "%2hx", a); +} + +/* Exercise all integer specifiers with no modifier and a non-constant + argument. */ + +void test_sprintf_chk_int_nonconst (int a) +{ + T (-1, "%d", a); + + T (0, "%d", a); /* { dg-warning "into a region" } */ + T (0, "%i", a); /* { dg-warning "into a region" } */ + T (0, "%u", a); /* { dg-warning "into a region" } */ + T (0, "%x", a); /* { dg-warning "into a region" } */ + + T (1, "%d", a); /* { dg-warning "nul past the end" } */ + T (1, "%i", a); /* { dg-warning "nul past the end" } */ + T (1, "%u", a); /* { dg-warning "nul past the end" } */ + T (1, "%x", a); /* { dg-warning "nul past the end" } */ + + T (1, "% d", a); /* { dg-warning "into a region" } */ + T (1, "% i", a); /* { dg-warning "into a region" } */ + T (1, "%+d", a); /* { dg-warning "into a region" } */ + T (1, "%+i", a); /* { dg-warning "into a region" } */ + T (1, "%-d", a); /* { dg-warning "nul past the end" } */ + T (1, "%-i", a); /* { dg-warning "nul past the end" } */ + + T (2, "%d", a); + T (2, "%i", a); + T (2, "%o", a); + T (2, "%u", a); + T (2, "%x", a); + + T (2, "% d", a); /* { dg-warning "nul past the end" } */ + T (2, "% i", a); /* { dg-warning "nul past the end" } */ + T (2, "% o", a); /* { dg-warning ". . flag used with .%o." } */ + T (2, "% u", a); /* { dg-warning ". . flag used with .%u." } */ + T (2, "% x", a); /* { dg-warning ". . flag used with .%x." } */ + + T (2, "#%o", a); /* { dg-warning "nul past the end" } */ + T (2, "#%x", a); /* { dg-warning "nul past the end" } */ + + T (3, "%2d", a); + T (3, "%2i", a); + T (3, "%2o", a); + T (3, "%2u", a); + T (3, "%2x", a); +} + +void test_sprintf_chk_e_nonconst (double d) +{ + T (-1, "%E", d); + T ( 0, "%E", d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ + T ( 0, "%e", d); /* { dg-warning "into a region" } */ + T ( 1, "%E", d); /* { dg-warning "into a region" } */ + T ( 1, "%e", d); /* { dg-warning "into a region" } */ + T ( 2, "%e", d); /* { dg-warning "into a region" } */ + T ( 3, "%e", d); /* { dg-warning "into a region" } */ + T (12, "%e", d); /* { dg-warning "past the end" } */ + T (12, "%e", d); /* { dg-warning "past the end" } */ + T (13, "%E", d); /* 1.000000E+00 */ + T (13, "%e", d); + T (14, "%E", d); + T (14, "%e", d); + + T (0, "%+E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ + T (0, "%-e", d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ + T (0, "% E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ + + /* The range of output of "%.0e" is between 5 and 7 bytes (not counting + the terminating NUL. */ + T ( 5, "%.0e", d); /* { dg-warning "writing a terminating nul past the end" } */ + T ( 6, "%.0e", d); /* 1e+00 */ + + /* The range of output of "%.1e" is between 7 and 9 bytes (not counting + the terminating NUL. */ + T ( 7, "%.1e", d); /* { dg-warning "writing a terminating nul past the end" } */ + T ( 8, "%.1e", d); +} + +void test_sprintf_chk_f_nonconst (double d) +{ + T (-1, "%F", d); + T ( 0, "%F", d); /* { dg-warning "into a region" } */ + T ( 0, "%f", d); /* { dg-warning "into a region" } */ + T ( 1, "%F", d); /* { dg-warning "into a region" } */ + T ( 1, "%f", d); /* { dg-warning "into a region" } */ + T ( 2, "%F", d); /* { dg-warning "into a region" } */ + T ( 2, "%f", d); /* { dg-warning "into a region" } */ + T ( 3, "%F", d); /* { dg-warning "into a region" } */ + T ( 3, "%f", d); /* { dg-warning "into a region" } */ + T ( 4, "%F", d); /* { dg-warning "into a region" } */ + T ( 4, "%f", d); /* { dg-warning "into a region" } */ + T ( 5, "%F", d); /* { dg-warning "into a region" } */ + T ( 5, "%f", d); /* { dg-warning "into a region" } */ + T ( 6, "%F", d); /* { dg-warning "into a region" } */ + T ( 6, "%f", d); /* { dg-warning "into a region" } */ + T ( 7, "%F", d); /* { dg-warning "into a region" } */ + T ( 7, "%f", d); /* { dg-warning "into a region" } */ + T ( 8, "%F", d); /* { dg-warning "nul past the end" } */ + T ( 8, "%f", d); /* { dg-warning "nul past the end" } */ + T ( 9, "%F", d); + T ( 9, "%f", d); +} + +/* Tests for __builtin_vsprintf_chk are the same as those for + __builtin_sprintf_chk with non-constant arguments. */ +#undef T +#define T(size, fmt) \ + __builtin___vsprintf_chk (buffer (size), 0, objsize (size), fmt, va) + +void test_vsprintf_chk_c (__builtin_va_list va) +{ + T (-1, "%c"); + + /* Verify the full text of the diagnostic for just the distinct messages + and use abbreviations in subsequent test cases. */ + T (0, "%c"); /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */ + T (1, "%c"); /* { dg-warning "writing a terminating nul past the end" } */ + T (1, "%c"); /* { dg-warning "nul past the end" } */ + T (2, "%c"); + T (2, "%2c"); /* { dg-warning "nul past the end" } */ + T (2, "%3c"); /* { dg-warning "into a region" } */ + T (2, "%c%c"); /* { dg-warning "nul past the end" } */ + T (3, "%c%c"); + + /* Wide characters. */ + T (0, "%lc"); /* { dg-warning "nul past the end" } */ + T (1, "%lc"); + T (2, "%lc"); + + /* The following could result in as few as a single byte and in as many + as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property + the write cannot be reliably diagnosed. */ + T (2, "%lc"); + T (2, "%1lc"); + /* Writing some unknown number of bytes into a field two characters wide. */ + T (2, "%2lc"); /* { dg-warning "nul past the end" } */ + T (2, "%lc%lc"); + + T (3, "%lc%c"); + /* Here in the best case each argument will format as single character, + causing the terminating NUL to be written past the end. */ + T (3, "%lc%c%c"); + +} + +void test_vsprintf_chk_int (__builtin_va_list va) +{ + T (-1, "%d"); + + T (0, "%d"); /* { dg-warning "into a region" } */ + T (0, "%i"); /* { dg-warning "into a region" } */ + T (0, "%u"); /* { dg-warning "into a region" } */ + T (0, "%x"); /* { dg-warning "into a region" } */ + + T (1, "%d"); /* { dg-warning "nul past the end" } */ + T (1, "%i"); /* { dg-warning "nul past the end" } */ + T (1, "%u"); /* { dg-warning "nul past the end" } */ + T (1, "%x"); /* { dg-warning "nul past the end" } */ + + T (1, "% d"); /* { dg-warning "into a region" } */ + T (1, "% i"); /* { dg-warning "into a region" } */ + T (1, "%+d"); /* { dg-warning "into a region" } */ + T (1, "%+i"); /* { dg-warning "into a region" } */ + T (1, "%-d"); /* { dg-warning "nul past the end" } */ + T (1, "%-i"); /* { dg-warning "nul past the end" } */ + + T (2, "%d"); + T (2, "%i"); + T (2, "%o"); + T (2, "%u"); + T (2, "%x"); + + T (2, "% d"); /* { dg-warning "nul past the end" } */ + T (2, "% i"); /* { dg-warning "nul past the end" } */ + T (2, "% o"); /* { dg-warning ". . flag used with .%o." } */ + T (2, "% u"); /* { dg-warning ". . flag used with .%u." } */ + T (2, "% x"); /* { dg-warning ". . flag used with .%x." } */ + + T (2, "#%o"); /* { dg-warning "nul past the end" } */ + T (2, "#%x"); /* { dg-warning "nul past the end" } */ + + T (3, "%2d"); + T (3, "%2i"); + T (3, "%2o"); + T (3, "%2u"); + T (3, "%2x"); +} + +#undef T +#define T(size, fmt, ...) \ + __builtin_snprintf (buffer (size), objsize (size), fmt, __VA_ARGS__) + +void test_snprintf_c_const (void) +{ + T (-1, "%c", 0); /* { dg-warning "specified destination size \[0-9\]+ too large" } */ + + /* Verify the full text of the diagnostic for just the distinct messages + and use abbreviations in subsequent test cases. */ + + /* A call to snprintf with a buffer of zero size is a request to determine + the size of output without writing anything into the destination. No + warning must be issued. */ + T (0, "%c", 0); + T (1, "%c", 0); /* { dg-warning "output truncated before the last format character" } */ + T (1, "%c", '1'); /* { dg-warning "output truncated" } */ + T (2, "%c", '1'); + T (2, "%2c", '1'); /* { dg-warning "output truncated" } */ + T (2, "%3c", '1'); /* { dg-warning "directive output truncated" } */ + T (2, "%c%c", '1', '2'); /* { dg-warning "output truncated" } */ + T (3, "%c%c", '1', '2'); + + /* Wide characters. */ + T (0, "%lc", 0); + T (1, "%lc", 0); + T (2, "%lc", 0); + + /* The following could result in as few as a single byte and in as many + as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property + the write cannot be reliably diagnosed. */ + T (2, "%lc", L'1'); + T (2, "%1lc", L'1'); + /* Writing at least 1 characted into a field two characters wide. */ + T (2, "%2lc", L'1'); /* { dg-warning "output truncated before the last format character" } */ + + T (3, "%lc%c", L'1', '2'); + /* Here in the best case each argument will format as single character, + causing the output to be truncated just before the terminating NUL + (i.e., cutting off the '3'). */ + T (3, "%lc%c%c", L'1', '2', '3'); /* { dg-warning "output truncated" } */ + T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */ +} + +#undef T +#define T(size, fmt, ...) \ + __builtin___snprintf_chk (buffer (size), objsize (size), \ + 0, objsize (size), fmt, __VA_ARGS__) + +void test_snprintf_chk_c_const (void) +{ + /* Verify that specifying a size of the destination buffer that's + bigger than its actual size (normally determined and passed to + the function by __builtin_object_size) is diagnosed. */ + __builtin___snprintf_chk (buffer, 3, 0, 2, " "); /* { dg-warning "always overflow|specified size 3 exceeds the size 2 of the destination" } */ + + T (-1, "%c", 0); /* { dg-warning "specified destination size \[^ \]* too large" } */ + + T (0, "%c", 0); + T (0, "%c%c", 0, 0); + T (0, "%c_%c", 0, 0); + T (0, "_%c_%c", 0, 0); + + T (1, "%c", 0); /* { dg-warning "output truncated before the last format character" } */ + T (1, "%c", '1'); /* { dg-warning "output truncated" } */ + T (2, "%c", '1'); + T (2, "%2c", '1'); /* { dg-warning "output truncated" } */ + T (2, "%3c", '1'); /* { dg-warning "directive output truncated" } */ + T (2, "%c%c", '1', '2'); /* { dg-warning "output truncated before the last format character" } */ + T (3, "%c%c", '1', '2'); + T (3, "%c_%c", '1', '2'); /* { dg-warning "output truncated" } */ + + /* Wide characters. */ + T (0, "%lc", 0); + T (1, "%lc", 0); + T (2, "%lc", 0); + + /* The following could result in as few as a single byte and in as many + as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property + the write cannot be reliably diagnosed. */ + T (2, "%lc", L'1'); + T (2, "%1lc", L'1'); + /* Writing at least 1 characted into a field two characters wide. */ + T (2, "%2lc", L'1'); /* { dg-warning "output truncated before the last format character" } */ + + T (3, "%lc%c", L'1', '2'); + /* Here in the best case each argument will format as single character, + causing the output to be truncated just before the terminating NUL + (i.e., cutting off the '3'). */ + T (3, "%lc%c%c", L'1', '2', '3'); /* { dg-warning "output truncated" } */ + T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */ +} + +/* Macro to verify that calls to __builtin_vsprintf (i.e., with no size + argument) issue diagnostics by correctly determining the size of + the destination buffer. */ +#undef T +#define T(size, fmt) \ + __builtin_vsprintf (buffer (size), fmt, va) + +void test_vsprintf_s (__builtin_va_list va) +{ + T (-1, "%s"); + + T (0, "%s"); /* { dg-warning "writing a terminating nul past the end" } */ + T (1, "%s"); + T (1, "%1s"); /* { dg-warning "writing a terminating nul past the end" } */ + + T (2, "%s%s"); + T (2, "%s%s_"); + T (2, "%s_%s"); + T (2, "_%s%s"); + T (2, "_%s_%s"); /* { dg-warning "writing a terminating nul past the end" } */ +} + +/* Exercise all integer specifiers with no modifier and a non-constant + argument. */ + +void test_vsprintf_int (__builtin_va_list va) +{ + T (-1, "%d"); + + T (0, "%d"); /* { dg-warning "into a region" } */ + T (0, "%i"); /* { dg-warning "into a region" } */ + T (0, "%u"); /* { dg-warning "into a region" } */ + T (0, "%x"); /* { dg-warning "into a region" } */ + + T (1, "%d"); /* { dg-warning "nul past the end" } */ + T (1, "%i"); /* { dg-warning "nul past the end" } */ + T (1, "%u"); /* { dg-warning "nul past the end" } */ + T (1, "%x"); /* { dg-warning "nul past the end" } */ + + T (1, "% d"); /* { dg-warning "into a region" } */ + T (1, "% i"); /* { dg-warning "into a region" } */ + T (1, "%+d"); /* { dg-warning "into a region" } */ + T (1, "%+i"); /* { dg-warning "into a region" } */ + T (1, "%-d"); /* { dg-warning "nul past the end" } */ + T (1, "%-i"); /* { dg-warning "nul past the end" } */ + + T (2, "%d"); + T (2, "%i"); + T (2, "%o"); + T (2, "%u"); + T (2, "%x"); + + T (2, "% d"); /* { dg-warning "nul past the end" } */ + T (2, "% i"); /* { dg-warning "nul past the end" } */ + T (2, "% o"); /* { dg-warning ". . flag used with .%o." } */ + T (2, "% u"); /* { dg-warning ". . flag used with .%u." } */ + T (2, "% x"); /* { dg-warning ". . flag used with .%x." } */ + + T (2, "#%o"); /* { dg-warning "nul past the end" } */ + T (2, "#%x"); /* { dg-warning "nul past the end" } */ + + T (3, "%2d"); + T (3, "%2i"); + T (3, "%2o"); + T (3, "%2u"); + T (3, "%2x"); +} + +#undef T +#define T(size, fmt) \ + __builtin_vsnprintf (buffer (size), objsize (size), fmt, va) + +void test_vsnprintf_s (__builtin_va_list va) +{ + T (-1, "%s"); /* { dg-warning "specified destination size \[^ \]* too large" } */ + + T (0, "%s"); + T (1, "%s"); + T (1, "%1s"); /* { dg-warning "output truncated before the last format character" } */ + + T (2, "%s%s"); + T (2, "%s%s_"); + T (2, "%s_%s"); + T (2, "_%s%s"); + T (2, "_%s_%s"); /* { dg-warning "output truncated before the last format character" } */ +} + +#undef T +#define T(size, fmt) \ + __builtin___vsnprintf_chk (buffer (size), objsize (size), \ + 0, objsize (size), fmt, va) + +void test_vsnprintf_chk_s (__builtin_va_list va) +{ + /* Verify that specifying a size of the destination buffer that's + bigger than its actual size (normally determined and passed to + the function by __builtin_object_size) is diagnosed. */ + __builtin___snprintf_chk (buffer, 123, 0, 122, " "); /* { dg-warning "always overflow|specified size 123 exceeds the size 122 of the destination object" } */ + + __builtin___snprintf_chk (buffer, __SIZE_MAX__, 0, 2, " "); /* { dg-warning "always overflow|destination size .\[0-9\]+. too large" } */ + + T (0, "%s"); + T (1, "%s"); + T (1, "%1s"); /* { dg-warning "output truncated before the last format character" } */ + + T (2, "%s%s"); + T (2, "%s%s_"); + T (2, "%s_%s"); + T (2, "_%s%s"); + T (2, "_%s_%s"); /* { dg-warning "output truncated before the last format character" } */ +} diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c new file mode 100644 index 00000000000..b0f2d450077 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c @@ -0,0 +1,214 @@ +/* { dg-do compile } */ +/* { dg-options "-std=c99 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */ + +/* 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 + +char buffer [256]; +extern char *ptr; + +#define buffer(size) \ + (!LINE || __LINE__ == LINE ? buffer + sizeof buffer - size : ptr) + +#define objsize(size) (!LINE || __LINE__ == LINE ? size : __SIZE_MAX__ / 2) + +typedef __SIZE_TYPE__ size_t; + +#if !__cplusplus +typedef __WCHAR_TYPE__ wchar_t; +#endif + +typedef unsigned char UChar; + +#define T(size, fmt, ...) \ + __builtin_sprintf (buffer (size), fmt, __VA_ARGS__) + +__builtin_va_list va; + +/* Exercise buffer overflow detection with const string arguments. */ + +void test_s_const (void) +{ + /* Wide string literals are handled slightly differently than + at level 1. At level 1, each wide character is assumed to + convert into a single byte. At level 2, they are assumed + to convert into at least one byte. */ + T (0, "%ls", L""); /* { dg-warning "nul past the end" } */ + T (1, "%ls", L""); + T (1, "%ls", L"\0"); + T (1, "%1ls", L""); /* { dg-warning "nul past the end" } */ + + T (0, "%*ls", 0, L""); /* { dg-warning "nul past the end" } */ + T (1, "%*ls", 0, L""); + T (1, "%*ls", 0, L"\0"); + T (1, "%*ls", 1, L""); /* { dg-warning "nul past the end" } */ + + T (1, "%ls", L"1"); /* { dg-warning "nul past the end" } */ + T (1, "%.0ls", L"1"); + T (2, "%.0ls", L"1"); + T (2, "%.1ls", L"1"); + + /* The "%.2ls" directive below will write at a minimum 1 byte (because + L"1" is known and can be assumed to convert to at least one multibyte + character), and at most 2 bytes because of the precision. Since its + output is explicitly bounded it is diagnosed. */ + T (2, "%.2ls", L"1"); /* { dg-warning "nul past the end" } */ + T (2, "%.*ls", 2, L"1"); /* { dg-warning "nul past the end" } */ + + /* The following three are constrained by the precision to at most + that many bytes of the converted wide string plus a terminating NUL. */ + T (2, "%.0ls", L"1"); + T (2, "%.1ls", L"1"); + T (3, "%.2ls", L"1"); +} + + +struct Arrays { + char a1 [1]; + char a2 [2]; + char a3 [3]; + char a4 [4]; + char a0 [0]; + char ax []; +}; + +/* Exercise buffer overflow detection with non-const string arguments. */ + +void test_s_nonconst (const char *s, const wchar_t *ws, struct Arrays *a) +{ + T (0, "%s", s); /* { dg-warning "into a region" "sprintf transformed into strcpy" { xfail *-*-*-* } } */ + T (1, "%s", s); /* { dg-warning "nul past the end" "sprintf transformed into strcpy" { xfail *-*-*-* } } */ + T (1, "%1s", s); /* { dg-warning "nul past the end" } */ + T (1, "%.0s", s); + T (1, "%.1s", s); /* { dg-warning "writing a terminating nul" } */ + + T (1, "%ls", ws); /* { dg-warning "writing a terminating nul" } */ + + /* Verify that the size of the array is used in lieu of its length. + The minus sign disables GCC's sprintf to strcpy transformation. */ + T (1, "%-s", a->a1); /* { dg-warning "nul past the end" } */ + + /* In the following test, since the length of the strings isn't known, + their type (the array) is used to bound the maximum length to 1, + which means the "%-s" directive would not overflow the buffer, + but it would leave no room for the terminating nul. */ + T (1, "%-s", a->a2); /* { dg-warning "writing a terminating nul" } */ + + /* Unlike in the test above, since the length of the string is bounded + by the array type to at most 2, the "^-s" directive is diagnosed firts, + preventing the diagnostic about the terminatinb nul. */ + T (1, "%-s", a->a3); /* { dg-warning "directive writing between 1 and 2 bytes" } */ + + /* The length of a zero length array and flexible array member is + unknown and at leve 2 assumed to be at least 1. */ + T (1, "%-s", a->a0); /* { dg-warning "nul past the end" } */ + T (1, "%-s", a->ax); /* { dg-warning "nul past the end" } */ + + T (2, "%-s", a->a0); + T (2, "%-s", a->ax); +} + + /* Exercise buffer overflow detection with non-const integer arguments. */ + +void test_hh_nonconst (int x) +{ + T (1, "%hhi", x); /* { dg-warning "into a region" } */ + T (2, "%hhi", x); /* { dg-warning "into a region" } */ + T (3, "%hhi", x); /* { dg-warning "into a region" } */ + T (4, "%hhi", x); /* { dg-warning "may write a terminating nul past the end of the destination" } */ +} + +void test_h_nonconst (int x) +{ + extern UChar uc; + + T (1, "%hi", uc); /* { dg-warning "into a region" } */ + T (2, "%hi", uc); /* { dg-warning "into a region" } */ + /* Formatting an 8-bit unsigned char as a signed short (or any other + type with greater precision) can write at most 3 characters. */ + T (3, "%hi", uc); /* { dg-warning "terminating nul past" } */ + T (4, "%hi", uc); + + /* Verify that the same thing works when the int argument is cast + to unsigned char. */ + T (1, "%hi", (UChar)x); /* { dg-warning "into a region" } */ + T (2, "%hi", (UChar)x); /* { dg-warning "into a region" } */ + T (3, "%hi", (UChar)x); /* { dg-warning "may write a terminating nul past the end of the destination" } */ + T (4, "%hi", (UChar)x); +} + +void test_i_nonconst (int x) +{ + extern UChar uc; + + T (1, "%i", uc); /* { dg-warning "into a region" } */ + T (2, "%i", uc); /* { dg-warning "into a region" } */ + T (3, "%i", uc); /* { dg-warning "terminating nul past" } */ + T (4, "%i", uc); + + T (1, "%i", (UChar)x); /* { dg-warning "into a region" } */ + T (2, "%i", (UChar)x); /* { dg-warning "into a region" } */ + T (3, "%i", (UChar)x); /* { dg-warning "terminating nul past" } */ + T (4, "%i", (UChar)x); + + /* Verify the same thing using a bit-field. */ + extern struct { + unsigned int b1: 1; + unsigned int b2: 2; + unsigned int b3: 3; + unsigned int b4: 4; + int sb4: 4; + unsigned int b5: 5; + unsigned int b6: 6; + unsigned int b7: 7; + unsigned int b8: 8; + } bf, abf[], *pbf; + + T (1, "%i", bf.b1); /* { dg-warning "nul past the end" } */ + T (1, "%i", abf [x].b1); /* { dg-warning "nul past the end" } */ + T (1, "%i", pbf->b1); /* { dg-warning "nul past the end" } */ + /* A one bit bit-field can only be formatted as '0' or '1'. Similarly, + two- and three-bit bit-fields can only be formatted as a single + decimal digit. */ + T (2, "%i", bf.b1); + T (2, "%i", abf [x].b1); + T (2, "%i", pbf->b1); + T (2, "%i", bf.b2); + T (2, "%i", abf [x].b2); + T (2, "%i", pbf->b2); + T (2, "%i", bf.b3); + T (2, "%i", abf [x].b3); + T (2, "%i", pbf->b3); + /* A four-bit bit-field can be formatted as either one or two digits. */ + T (2, "%i", bf.b4); /* { dg-warning "nul past the end" } */ + T (2, "%i", abf [x].b4); /* { dg-warning "nul past the end" } */ + T (2, "%i", pbf->b4); /* { dg-warning "nul past the end" } */ + + T (3, "%i", bf.b4); + T (3, "%i", pbf->b4); + T (3, "%i", bf.b5); + T (3, "%i", pbf->b5); + T (3, "%i", bf.b6); + T (3, "%i", pbf->b6); + T (3, "%i", bf.b7); /* { dg-warning "nul past the end" } */ + T (3, "%i", pbf->b7); /* { dg-warning "nul past the end" } */ + + T (1, "%i", bf.b8); /* { dg-warning "into a region" } */ + T (2, "%i", bf.b8); /* { dg-warning "into a region" } */ + /* Formatting an 8-bit unsigned char as a signed short (or any other + type with greater precision) int can write at most 3 characters. */ + T (3, "%i", bf.b8); /* { dg-warning "terminating nul past" } */ + T (4, "%i", bf.b8); + + T (1, "%i", bf.b8); /* { dg-warning "into a region" } */ + T (2, "%i", bf.b8); /* { dg-warning "into a region" } */ + T (3, "%i", bf.b8); /* { dg-warning "terminating nul past" } */ + + T (2, "%i", bf.sb4); /* { dg-warning "terminating nul past" } */ + T (3, "%i", bf.sb4); + T (4, "%i", bf.sb4); +} diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c new file mode 100644 index 00000000000..625d055bafb --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c @@ -0,0 +1,234 @@ +/* { dg-do compile } */ +/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */ + +#ifndef LINE +# define LINE 0 +#endif + +#define bos(x) __builtin_object_size (x, 0) + +#define T(bufsize, fmt, ...) \ + do { \ + if (!LINE || __LINE__ == LINE) \ + { \ + char *d = (char *)__builtin_malloc (bufsize); \ + __builtin___sprintf_chk (d, 0, bos (d), fmt, __VA_ARGS__); \ + sink (d); \ + } \ + } while (0) + +void +sink (void*); + +/* Identity function to verify that the checker figures out the value + of the operand even when it's not constant (i.e., makes use of + inlining and constant propagation information). */ + +int i (int x) { return x; } +const char* s (const char *str) { return str; } + +/* Function to "generate" a unique unknown number (as far as GCC can + tell) each time it's called. It prevents the optimizer from being + able to narrow down the ranges of possible values in test functions + with repeated references to the same variable. */ +extern int x (void); + +/* Verify that the checker can detect buffer overflow when the "%s" + argument is in a known range of lengths and one or both of which + exceed the size of the destination. */ + +void test_sprintf_chk_string (const char *s, const char *t) +{ +#define x x () + + T (1, "%s", x ? "" : "1"); /* { dg-warning "nul past the end" } */ + T (1, "%s", x ? "1" : ""); /* { dg-warning "nul past the end" } */ + T (1, "%s", x ? s : "1"); /* { dg-warning "nul past the end" } */ + T (1, "%s", x ? "1" : s); /* { dg-warning "nul past the end" } */ + T (1, "%s", x ? s : t); + + T (2, "%s", x ? "" : "1"); + T (2, "%s", x ? "" : s); + T (2, "%s", x ? "1" : ""); + T (2, "%s", x ? s : ""); + T (2, "%s", x ? "1" : "2"); + T (2, "%s", x ? "" : "12"); /* { dg-warning "nul past the end" } */ + T (2, "%s", x ? "12" : ""); /* { dg-warning "nul past the end" } */ + + T (2, "%s", x ? "" : "123"); /* { dg-warning "into a region" } */ + T (2, "%s", x ? "123" : ""); /* { dg-warning "into a region" } */ + +#undef x +} + + +/* Verify that the checker makes use of integer constant propagation + to detect buffer overflow in non-constant cases. */ + +void test_sprintf_chk_integer_value (void) +{ + T ( 1, "%i", i ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%i", i ( 1)); /* { dg-warning "nul past the end" } */ + T ( 1, "%i", i ( -1)); /* { dg-warning "into a region" } */ + T ( 1, "%i_", i ( 1)); /* { dg-warning "character ._. at offset 2 past the end" } */ + T ( 1, "_%i", i ( 1)); /* { dg-warning "into a region" } */ + T ( 1, "_%i_",i ( 1)); /* { dg-warning "into a region" } */ + T ( 1, "%o", i ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%u", i ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%x", i ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%#x", i ( 0)); /* { dg-warning "nul past the end" } */ + T ( 1, "%x", i ( 1)); /* { dg-warning "nul past the end" } */ + T ( 1, "%#x", i ( 1)); /* { dg-warning "into a region" } */ + + T ( 2, "%i", i ( 0)); + T ( 2, "%i", i ( 1)); + T ( 2, "%i", i ( 9)); + T ( 2, "%i", i ( -1)); /* { dg-warning "nul past the end" } */ + T ( 2, "%i", i ( 10)); /* { dg-warning "nul past the end" } */ + T ( 2, "%i_", i ( 0)); /* { dg-warning "nul past the end" } */ + T ( 2, "_%i", i ( 0)); /* { dg-warning "nul past the end" } */ + T ( 2, "_%i_",i ( 0)); /* { dg-warning "character ._. at offset 3 past the end" } */ + T ( 2, "%o", i ( 1)); + T ( 2, "%o", i ( 7)); + T ( 2, "%o", i ( 010)); /* { dg-warning "nul past the end" } */ + T ( 2, "%o", i ( 0100)); /* { dg-warning "into a region" } */ + T ( 2, "%x", i ( 1)); + T ( 2, "%#x", i ( 1)); /* { dg-warning "into a region" } */ + T ( 2, "%x", i ( 0xa)); + T ( 2, "%x", i ( 0xf)); + T ( 2, "%x", i ( 0x10)); /* { dg-warning "nul past the end" } */ + T ( 2, "%x", i ( 0xff)); /* { dg-warning "nul past the end" } */ + T ( 2, "%x", i (0x1ff)); /* { dg-warning "into a region" } */ + + T ( 3, "%i", i ( 0)); + T ( 3, "%i", i ( 1)); + T ( 3, "%i", i ( 9)); + T ( 3, "%i", i ( -9)); + T ( 3, "%i", i ( 10)); + T ( 3, "%i", i ( 99)); + T ( 3, "%i", i ( -99)); /* { dg-warning "nul past the end" } */ + + T ( 3, "%i", i (99) + i (1)); /* { dg-warning "nul past the end" } */ + + T ( 8, "%8u", i ( 1)); /* { dg-warning "nul past the end" } */ + T ( 9, "%8u", i ( 1)); +} + +/* Functions to require optimization to figure out the range of the operand. + Used to verify that the checker makes use of the range information to + avoid diagnosing the output of sufficiently constrained arguments to + integer directives. */ + +signed char* +range_schar (signed char *val, signed char min, signed char max) +{ + if (*val < min || max < *val) __builtin_abort (); + return val; +} + +unsigned char* +range_uchar (unsigned char *val, unsigned char min, unsigned char max) +{ + if (*val < min || max < *val) __builtin_abort (); + return val; +} + +signed short* +range_sshort (signed short *val, signed short min, signed short max) +{ + if (*val < min || max < *val) __builtin_abort (); + return val; +} + +unsigned short* +range_ushort (unsigned short *val, unsigned short min, unsigned short max) +{ + if (*val < min || max < *val) __builtin_abort (); + return val; +} + +/* Helper to prevent GCC from figuring out the return value. */ +extern int idx (void); + +/* Exercise ranges only in types signed and unsigned char and short. + No other types work due to bug 71690. */ + +void test_sprintf_chk_range_schar (signed char *a) +{ + (void)&a; + + /* Ra creates a range of signed char for A [idx]. A different + value is used each time to prevent the ranges from intesecting + one another, possibly even eliminating some tests as a result + of the range being empty. */ +#define R(min, max) *range_schar (a + idx (), min, max) + + T ( 0, "%i", R (0, 9)); /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */ + T ( 1, "%i", R (0, 9)); /* { dg-warning "nul past the end" } */ + T ( 2, "%i", R (0, 9)); + T ( 2, "%i", R (-1, 0)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ + T ( 2, "%i", R (9, 10)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ + + T ( 3, "%i", R ( -9, 9)); + T ( 3, "%i", R (-99, 99)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ + T ( 3, "%i", R ( 0, 99)); + T ( 3, "%i", R ( 0, 100)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ + + /* The following call may write as few as 3 bytes and as many as 5. + It's judgment call how best to diagnose it to make the potential + problem clear. */ + T ( 3, "%i%i", R (1, 10), R (9, 10)); /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */ + + T ( 4, "%i%i", R (10, 11), R (12, 13)); /* { dg-warning "nul past the end" } */ + + T ( 5, "%i%i", R (-9, 99), R (-9, 99)); + + T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 9)); + T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 10)); /* { dg-warning "may write a terminating nul past the end" } */ + T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 9)); /* { dg-warning "may write a terminating nul past the end" } */ + T ( 6, "%i_%i_%i", R (0, 10), R (0, 9), R (0, 9)); /* { dg-warning "may write a terminating nul past the end" } */ + T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */ +} + +void test_sprintf_chk_range_uchar (unsigned char *a, unsigned char *b) +{ + (void)&a; + (void)&b; + +#undef Ra +#define Ra(min, max) *range_uchar (a + idx (), min, max) + + T ( 0, "%i", Ra (0, 9)); /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */ + T ( 1, "%i", Ra (0, 9)); /* { dg-warning "nul past the end" } */ + T ( 2, "%i", Ra (0, 9)); + T ( 2, "%i", Ra (9, 10)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ + + T ( 3, "%i", Ra (0, 99)); + T ( 3, "%i", Ra (0, 100)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ +} + +void test_sprintf_chk_range_sshort (signed short *a, signed short *b) +{ + (void)&a; + (void)&b; + +#undef Ra +#define Ra(min, max) *range_sshort (a + idx (), min, max) + + T ( 0, "%i", Ra ( 0, 9)); /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */ + T ( 1, "%i", Ra ( 0, 1)); /* { dg-warning "nul past the end" } */ + T ( 1, "%i", Ra ( 0, 9)); /* { dg-warning "nul past the end" } */ + T ( 2, "%i", Ra ( 0, 1)); + T ( 2, "%i", Ra ( 8, 9)); + T ( 2, "%i", Ra ( 0, 9)); + T ( 2, "%i", Ra (-1, 0)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ + T ( 2, "%i", Ra ( 9, 10)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ + + T ( 3, "%i", Ra ( 0, 99)); + T ( 3, "%i", Ra (99, 999)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ + + T ( 4, "%i", Ra ( 0, 999)); + T ( 4, "%i", Ra ( 99, 999)); + T ( 4, "%i", Ra (998, 999)); + T ( 4, "%i", Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ +} diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c new file mode 100644 index 00000000000..944c36e242c --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c @@ -0,0 +1,33 @@ +/* { dg-do compile } */ +/* { dg-options "-Wformat -Wformat-length=1 -fdiagnostics-show-caret -ftrack-macro-expansion=0" } */ + +extern int sprintf (char*, const char*, ...); + +char dst [8]; + +void test (void) +{ + sprintf (dst + 7, "%-s", "1"); + /* { dg-warning "writing a terminating nul past the end of the destination" "" { target *-*-*-* } 10 } + { dg-message "format output 2 bytes into a destination of size 1" "" { target *-*-*-* } 10 } + { dg-begin-multiline-output "" } + sprintf (dst + 7, "%-s", "1"); + ^~~~~ + { dg-end-multiline-output "" } + { dg-begin-multiline-output "" } + sprintf (dst + 7, "%-s", "1"); + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ + + sprintf (dst + 7, "%-s", "abcd"); + /* { dg-warning ".%-s. directive writing 4 bytes into a region of size 1" "" { target *-*-*-* } 22 } + { dg-message "format output 5 bytes into a destination of size 1" "" { target *-*-*-* } 22 } + { dg-begin-multiline-output "" } + sprintf (dst + 7, "%-s", "abcd"); + ^~~ ~~~~~~ + { dg-end-multiline-output "" } + { dg-begin-multiline-output "" } + sprintf (dst + 7, "%-s", "abcd"); + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ +} diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c new file mode 100644 index 00000000000..1e50be15f05 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c @@ -0,0 +1,540 @@ +/* Test to verify that the return value of calls to __builtin_sprintf + that produce a known number of bytes on output is available for + constant folding. With optimization enabled the test will fail to + link if any of the assertions fails. Without optimization the test + aborts at runtime if any of the assertions fails. */ +/* { dg-do run } */ +/* { dg-additional-options "-O2 -Wall -Wno-pedantic -fprintf-return-value" } */ + +#ifndef LINE +# define LINE 0 +#endif + +#if __STDC_VERSION__ < 199901L +# define __func__ __FUNCTION__ +#endif + +typedef __SIZE_TYPE__ size_t; + +unsigned ntests; +unsigned nfails; + +void __attribute__ ((noclone, noinline)) +checkv (const char *func, int line, int res, int min, int max, + char *dst, const char *fmt, __builtin_va_list va) +{ + int n = __builtin_vsprintf (dst, fmt, va); + int len = __builtin_strlen (dst); + + ++ntests; + + int fail = 0; + if (n != res) + { + __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" " + "doesn't match function call return value: ", + func, line, fmt, dst); + if (min == max) + __builtin_printf ("%i != %i\n", n, min); + else + __builtin_printf ("%i not in [%i, %i]\n", n, min, max); + + fail = 1; + } + else + { + if (len < min || max < len) + { + __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" " + "doesn't match output length: ", + func, line, fmt, dst); + + if (min == max) + __builtin_printf ("%i != %i\n", len, min); + else + __builtin_printf ("%i not in [%i, %i]\n", len, min, max); + + fail = 1; + } + else + __builtin_printf ("PASS: %s:%i: \"%s\" result %i: \"%s\"\n", + func, line, fmt, n, dst); + } + + if (fail) + ++nfails; +} + +void __attribute__ ((noclone, noinline)) +check (const char *func, int line, int res, int min, int max, + char *dst, const char *fmt, ...) +{ + __builtin_va_list va; + __builtin_va_start (va, fmt); + checkv (func, line, res, min, max, dst, fmt, va); + __builtin_va_end (va); +} + +char buffer[256]; +char* volatile dst = buffer; +char* ptr = buffer; + +#define concat(a, b) a ## b +#define CAT(a, b) concat (a, b) + +#if __OPTIMIZE__ +/* With optimization references to the following undefined symbol which + is unique for each test case are expected to be eliminated. */ +# define TEST_FAILURE(line, ignore1, ignore2, ignore3) \ + do { \ + extern void CAT (failure_on_line_, line)(void); \ + CAT (failure_on_line_, line)(); \ + } while (0) +#else +/* The test is run by DejaGnu with optimization enabled. When it's run + with it disabled (i.e., at -O0) each test case is verified at runtime + and the test aborts just before exiting if any of them failed. */ +# define TEST_FAILURE(line, result, min, max) \ + if (min == max) \ + __builtin_printf ("FAIL: %s:%i: expected %i, got %i\n", \ + __func__, line, min, result); \ + else \ + __builtin_printf ("FAIL: %s:%i: expected range [%i, %i], got %i\n", \ + __func__, line, min, max, result); +#endif + +/* Verify that the result is exactly equal to RES. */ +#define EQL(expect, size, fmt, ...) \ + if (!LINE || LINE == __LINE__) \ + do { \ + char *buf = (size) < 0 ? ptr : buffer + sizeof buffer - (size); \ + int result = __builtin_sprintf (buf, fmt, __VA_ARGS__); \ + if (result != expect) \ + { \ + TEST_FAILURE (__LINE__, expect, expect, result); \ + } \ + check (__func__, __LINE__, result, expect, expect, dst, fmt, \ + __VA_ARGS__); \ + } while (0) + +/* Verify that the result is in the range [MIN, MAX]. */ +#define RNG(min, max, size, fmt, ...) \ + if (!LINE || LINE == __LINE__) \ + do { \ + char *buf = (size) < 0 ? ptr : buffer + sizeof buffer - (size); \ + int result = __builtin_sprintf (buf, fmt, __VA_ARGS__); \ + if (result < min || max < result) \ + { \ + TEST_FAILURE (__LINE__, min, max, result); \ + } \ + check (__func__, __LINE__, result, min, max, dst, fmt, \ + __VA_ARGS__); \ + } while (0) + +static void __attribute__ ((noinline, noclone)) +test_c (char c) +{ + EQL (1, 2, "%c", c); + EQL (1, -1, "%c", c); + EQL (1, 2, "%1c", c); + EQL (1, -1, "%1c", c); + EQL (1, 2, "%*c", 1, c); + EQL (1, -1, "%*c", 1, c); + EQL (2, 3, "%c%c", '1', '2'); + EQL (2, -1, "%c%c", '1', '2'); + EQL (3, 4, "%3c", c); + EQL (3, -1, "%3c", c); + EQL (3, 4, "%*c", 3, c); + EQL (3, -1, "%*c", 3, c); + + EQL (3, 4, "%*c%*c", 2, c, 1, c); + EQL (3, 4, "%*c%*c", 1, c, 2, c); + EQL (3, 4, "%c%c%c", '1', '2', '3'); + EQL (3, 4, "%*c%c%c", 1, '1', '2', '3'); + EQL (3, 4, "%*c%*c%c", 1, '1', 1, '2', '3'); + EQL (3, 4, "%*c%*c%*c", 1, '1', 1, '2', 1, '3'); + + EQL (3, -1, "%*c%*c", 2, c, 1, c); + EQL (3, -1, "%*c%*c", 1, c, 2, c); + EQL (3, -1, "%c%c%c", '1', '2', '3'); + EQL (3, -1, "%*c%c%c", 1, '1', '2', '3'); + EQL (3, -1, "%*c%*c%c", 1, '1', 1, '2', '3'); + EQL (3, -1, "%*c%*c%*c", 1, '1', 1, '2', 1, '3'); + + EQL (4, 5, "%c%c %c", '1', '2', '3'); + EQL (5, 6, "%c %c %c", '1', '2', '3'); + EQL (5, 6, "%c %c %c", c, c, c); +} + +/* Generate a pseudo-random value in the specified range. The return + value must be unsigned char to work around limitations in the GCC + range information. Similarly for the declaration of rand() whose + correct return value should be int, but that also prevents the range + information from making it to the printf pass. */ + +unsigned char uchar_range (unsigned min, unsigned max) +{ + extern unsigned rand (void); + + unsigned x; + x = rand (); + + if (x < min) + x = min; + else if (max < x) + x = max; + + return x; +} + +static void __attribute__ ((noinline, noclone)) +test_d_i (int i, long li) +{ + /* +-------------------------- expected return value */ + /* | +---------------------- destination size */ + /* | | +------------------- format string */ + /* | | | +-- variable argument(s) */ + /* | | | | */ + /* V V V V */ + EQL ( 1, 2, "%d", 0); + EQL ( 2, 3, "%d%d", 0, 1); + EQL ( 3, 4, "%d%d", 9, 10); + EQL ( 4, 5, "%d%d", 11, 12); + EQL ( 5, 6, "%d:%d", 12, 34); + EQL ( 5, 6, "%d", 12345); + EQL ( 6, 7, "%d", -12345); + EQL (15, 16, "%d:%d:%d:%d", 123, 124, 125, 126); + + EQL ( 1, 2, "%i", uchar_range (0, 9)); + EQL ( 1, -1, "%i", uchar_range (0, 9)); + + /* The range information available to passes other than the Value + Range Propoagation pass itself is so bad that the following two + tests fail (the range seen in the test below is [0, 99] rather + than [10, 99]. + EQL ( 2, 3, "%i", uchar_range (10, 99)); + EQL ( 3, 4, "%i", uchar_range (100, 199)); + */ + + /* Verify that the width allows the return value in the following + calls can be folded despite the unknown value of the argument. */ +#if __SIZEOF_INT__ == 2 + EQL ( 6, 7, "%6d", i); + EQL ( 6, 7, "%+6d", i); + EQL ( 6, 7, "%-6d", i); + EQL ( 6, 7, "%06d", i); +#elif __SIZEOF_INT__ == 4 + EQL (11, 12, "%11d", i); + EQL (11, 12, "%+11d", i); + EQL (11, 12, "%-11d", i); + EQL (11, 12, "%011d", i); +#elif __SIZEOF_INT__ == 8 + EQL (20, 21, "%20d", i); + EQL (20, 21, "%+20d", i); + EQL (20, 21, "%-29d", i); + EQL (20, 21, "%020d", i); +#endif + +#if __SIZEOF_LONG__ == 2 + EQL ( 6, 7, "%6ld", li); + EQL ( 6, 7, "%+6ld", li); + EQL ( 6, 7, "%-6ld", li); + EQL ( 6, 7, "%06ld", li); +#elif __SIZEOF_LONG__ == 4 + EQL (11, 12, "%11ld", li); + EQL (11, 12, "%+11ld", li); + EQL (11, 12, "%-11ld", li); + EQL (11, 12, "%011ld", li); +#elif __SIZEOF_LONG__ == 8 + EQL (20, 21, "%20ld", li); + EQL (20, 21, "%+20ld", li); + EQL (20, 21, "%-20ld", li); + EQL (20, 21, "%020ld", li); +#endif + + /* Verify that the output of a directive with an unknown argument + is correctly determined at compile time to be in the expected + range. */ + + /* +---------------------------- expected minimum return value */ + /* | +------------------------ expected maximum return value */ + /* | | +-------------------- destination size */ + /* | | | +----------------- format string */ + /* | | | | +----- variable argument(s) */ + /* | | | | | */ + /* V V V V V */ + RNG ( 1, 4, 5, "%hhi", i); + RNG ( 1, 3, 4, "%hhu", i); + +#if __SIZEOF_SHORT__ == 2 + RNG ( 1, 6, 7, "%hi", i); + RNG ( 1, 5, 6, "%hu", i); +#elif __SIZEOF_SHORT__ == 4 + RNG ( 1, 11, 12, "%hi", i); + RNG ( 1, 10, 11, "%hu", i); +#endif + +#if __SIZEOF_INT__ == 2 + RNG ( 1, 6, 7, "%i", i); + RNG ( 1, 5, 6, "%u", i); +#elif __SIZEOF_INT__ == 4 + RNG ( 1, 11, 12, "%i", i); + RNG ( 1, 10, 11, "%u", i); +#elif __SIZEOF_INT__ == 8 + RNG ( 1, 20, 21, "%i", i); + RNG ( 1, 19, 20, "%u", i); +#endif + +#if __SIZEOF_LONG__ == 4 + RNG ( 1, 11, 12, "%li", li); + RNG ( 1, 10, 11, "%lu", li); +#elif __SIZEOF_LONG__ == 8 + RNG ( 1, 20, 21, "%li", li); + RNG ( 1, 19, 20, "%lu", li); +#endif +} + +static void __attribute__ ((noinline, noclone)) +test_x (unsigned char uc, unsigned short us, unsigned ui) +{ + EQL ( 1, 2, "%hhx", 0); + EQL ( 2, 3, "%2hhx", 0); + EQL ( 2, 3, "%02hhx", 0); + EQL ( 2, 3, "%#02hhx", 0); + + EQL ( 1, 2, "%hhx", 1); + EQL ( 2, 3, "%2hhx", 1); + EQL ( 2, 3, "%02hhx", 1); + EQL ( 3, 4, "%#02hhx", 1); + + EQL ( 2, 3, "%2hhx", uc); + EQL ( 2, 3, "%02hhx", uc); + EQL ( 5, 6, "%#05hhx", uc); + + EQL ( 2, 3, "%2hhx", us); + EQL ( 2, 3, "%02hhx", us); + EQL ( 5, 6, "%#05hhx", us); + + EQL ( 2, 3, "%2hhx", ui); + EQL ( 2, 3, "%02hhx", ui); + EQL ( 5, 6, "%#05hhx", ui); + + EQL ( 1, 2, "%x", 0); + EQL ( 1, 2, "%#x", 0); + EQL ( 1, 2, "%#0x", 0); + EQL ( 1, 2, "%x", 1); + EQL ( 1, 2, "%x", 0xf); + EQL ( 2, 3, "%x", 0x10); + EQL ( 2, 3, "%x", 0xff); + EQL ( 3, 4, "%x", 0x100); + + EQL (11, 12, "%02x:%02x:%02x:%02x", 0xde, 0xad, 0xbe, 0xef); + + /* The following would be optimized if the range information of + the variable's type was made available. Alas, it's lost due + to the promotion of the actual argument (unsined char) to + the type of the "formal" argument (int in the case of the + ellipsis). + EQL (11, 12, "%02x:%02x:%02x:%02x", uc, uc, uc, uc); + */ + EQL (11, 12, "%02hhx:%02hhx:%02hhx:%02hhx", uc, uc, uc, uc); + +#if __SIZEOF_SHORT__ == 2 + EQL ( 4, 5, "%04hx", us); + EQL ( 9, 10, "%04hx:%04hx", us, us); + EQL (14, 15, "%04hx:%04hx:%04hx", us, us, us); + EQL (19, 20, "%04hx:%04hx:%04hx:%04hx", us, us, us, us); +#endif + +#if __SIZEOF_INT__ == 2 + EQL ( 4, 5, "%04x", ui); + EQL ( 6, 7, "%#06x", ui); +#elif __SIZEOF_INT__ == 4 + EQL ( 8, 9, "%08x", ui); + EQL (10, 10 + 1, "%#010x", ui); +#elif __SIZEOF_INT__ == 8 + EQL (16, 17, "%016x", ui); + EQL (18, 19, "%#018x", ui); +#endif +} + +static void __attribute__ ((noinline, noclone)) +test_a_double (void) +{ + EQL ( 6, 7, "%a", 0.0); /* 0x0p+0 */ + EQL ( 6, 7, "%a", 1.0); /* 0x8p-3 */ + EQL ( 6, 7, "%a", 2.0); /* 0x8p-2 */ + + EQL ( 8, 9, "%.1a", 3.0); /* 0xc.0p-2 */ + EQL ( 9, 10, "%.2a", 4.0); /* 0xa.00p-1 */ +} + +static void __attribute__ ((noinline, noclone)) +test_a_long_double (void) +{ + EQL ( 6, 7, "%La", 0.0L); /* 0x0p+0 */ + EQL ( 6, 7, "%La", 1.0L); /* 0x8p-3 */ + EQL ( 6, 7, "%La", 2.0L); /* 0x8p-2 */ + + EQL ( 8, 9, "%.1La", 3.0L); /* 0xc.0p-2 */ + EQL ( 9, 10, "%.2La", 4.0L); /* 0xa.00p-1 */ +} + +static void __attribute__ ((noinline, noclone)) +test_e_double (void) +{ + EQL (12, 13, "%e", 1.0e0); + EQL (13, 14, "%e", -1.0e0); + EQL (12, 13, "%e", 1.0e+1); + EQL (13, 14, "%e", -1.0e+1); + EQL (12, 13, "%e", 1.0e+12); + EQL (13, 14, "%e", -1.0e+12); + EQL (13, 14, "%e", 1.0e+123); + EQL (14, 15, "%e", -1.0e+123); + + EQL (12, 13, "%e", 9.999e+99); + EQL (12, 13, "%e", 9.9999e+99); + EQL (12, 13, "%e", 9.99999e+99); + + /* The actual output of the following directive depends on the rounding + mode. */ + /* EQL (12, "%e", 9.9999994e+99); */ + + EQL (12, 13, "%e", 1.0e-1); + EQL (12, 13, "%e", 1.0e-12); + EQL (13, 14, "%e", 1.0e-123); +} + +static void __attribute__ ((noinline, noclone)) +test_e_long_double (void) +{ + EQL (12, 13, "%Le", 1.0e0L); + EQL (13, 14, "%Le", -1.0e0L); + EQL (12, 13, "%Le", 1.0e+1L); + EQL (13, 14, "%Le", -1.0e+1L); + EQL (12, 13, "%Le", 1.0e+12L); + EQL (13, 14, "%Le", -1.0e+12L); + EQL (13, 14, "%Le", 1.0e+123L); + EQL (14, 15, "%Le", -1.0e+123L); + + EQL (12, 13, "%Le", 9.999e+99L); + EQL (12, 13, "%Le", 9.9999e+99L); + EQL (12, 13, "%Le", 9.99999e+99L); + EQL (12, 13, "%Le", 9.999999e+99L); + + /* The actual output of the following directive depends on the rounding + mode. */ + /* EQL (12, "%Le", 9.9999994e+99L); */ + + EQL (12, 13, "%Le", 1.0e-1L); + EQL (12, 13, "%Le", 1.0e-12L); + EQL (13, 14, "%Le", 1.0e-123L); + + EQL ( 6, 7, "%.0Le", 1.0e-111L); + EQL ( 8, 9, "%.1Le", 1.0e-111L); + EQL (19, 20, "%.12Le", 1.0e-112L); + EQL (20, 21, "%.13Le", 1.0e-113L); +} + +static void __attribute__ ((noinline, noclone)) +test_f_double (void) +{ + EQL ( 8, 9, "%f", 0.0e0); + EQL ( 8, 9, "%f", 0.1e0); + EQL ( 8, 9, "%f", 0.12e0); + EQL ( 8, 9, "%f", 0.123e0); + EQL ( 8, 9, "%f", 0.1234e0); + EQL ( 8, 9, "%f", 0.12345e0); + EQL ( 8, 9, "%f", 0.123456e0); + EQL ( 8, 9, "%f", 1.234567e0); + + EQL ( 9, 10, "%f", 1.0e+1); + EQL ( 20, 21, "%f", 1.0e+12); + EQL (130, 131, "%f", 1.0e+123); + + EQL ( 8, 9, "%f", 1.0e-1); + EQL ( 8, 9, "%f", 1.0e-12); + EQL ( 8, 9, "%f", 1.0e-123); +} + +static void __attribute__ ((noinline, noclone)) +test_f_long_double (void) +{ + EQL ( 8, 9, "%Lf", 0.0e0L); + EQL ( 8, 9, "%Lf", 0.1e0L); + EQL ( 8, 9, "%Lf", 0.12e0L); + EQL ( 8, 9, "%Lf", 0.123e0L); + EQL ( 8, 9, "%Lf", 0.1234e0L); + EQL ( 8, 9, "%Lf", 0.12345e0L); + EQL ( 8, 9, "%Lf", 0.123456e0L); + EQL ( 8, 9, "%Lf", 1.234567e0L); + + EQL ( 9, 10, "%Lf", 1.0e+1L); + EQL ( 20, 21, "%Lf", 1.0e+12L); + EQL (130, 131, "%Lf", 1.0e+123L); + + EQL ( 8, 9, "%Lf", 1.0e-1L); + EQL ( 8, 9, "%Lf", 1.0e-12L); + EQL ( 8, 9, "%Lf", 1.0e-123L); +} + +static void __attribute__ ((noinline, noclone)) +test_s (int i) +{ + EQL ( 0, 1, "%s", ""); + EQL ( 0, 1, "%s", "\0"); + EQL ( 1, 2, "%1s", ""); + EQL ( 1, 2, "%s", "1"); + EQL ( 2, 3, "%2s", ""); + EQL ( 2, 3, "%s", "12"); + EQL ( 2, 3, "%s%s", "12", ""); + EQL ( 2, 3, "%s%s", "", "12"); + EQL ( 2, 3, "%s%s", "1", "2"); + EQL ( 3, 4, "%3s", ""); + EQL ( 3, 4, "%3s", "1"); + EQL ( 3, 4, "%3s", "12"); + EQL ( 3, 4, "%3s", "123"); + EQL ( 3, 4, "%3.3s", "1"); + EQL ( 3, 4, "%3.3s", "12"); + EQL ( 3, 4, "%3.3s", "123"); + EQL ( 3, 4, "%3.3s", "1234"); + EQL ( 3, 4, "%3.3s", "12345"); + EQL ( 3, 4, "%s %s", "1", "2"); + EQL ( 4, 5, "%s %s", "12", "3"); + EQL ( 5, 6, "%s %s", "12", "34"); + EQL ( 5, 6, "[%s %s]", "1", "2"); + EQL ( 6, 7, "[%s %s]", "12", "3"); + EQL ( 7, 8, "[%s %s]", "12", "34"); + + /* Verify the result of a conditional expression involving string + literals is in the expected range of their lengths. */ + RNG ( 0, 3, 4, "%-s", i ? "" : "123"); + RNG ( 1, 4, 5, "%-s", i ? "1" : "1234"); + RNG ( 2, 5, 6, "%-s", i ? "12" : "12345"); + RNG ( 3, 6, 7, "%-s", i ? "123" : "123456"); +} + +int main (void) +{ + test_c ('?'); + test_d_i (0xdeadbeef, 0xdeadbeefL); + test_x ('?', 0xdead, 0xdeadbeef); + + test_a_double (); + test_e_double (); + test_f_double (); + + test_a_long_double (); + test_e_long_double (); + test_f_long_double (); + + test_s (0); + + if (nfails) + { + __builtin_printf ("%u out of %u tests failed\n", nfails, ntests); + __builtin_abort (); + } + + return 0; +} diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h index 251b30ffcdf..5903fde0bb5 100644 --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -471,6 +471,7 @@ extern simple_ipa_opt_pass *make_pass_ipa_oacc (gcc::context *ctxt); extern simple_ipa_opt_pass *make_pass_ipa_oacc_kernels (gcc::context *ctxt); extern gimple_opt_pass *make_pass_gen_hsail (gcc::context *ctxt); extern gimple_opt_pass *make_pass_warn_nonnull_compare (gcc::context *ctxt); +extern gimple_opt_pass *make_pass_sprintf_length (gcc::context *ctxt); /* IPA Passes */ extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt); -- 2.30.2