PR middle-end/49905 - Better sanity checking on sprintf src & dest to
produce warning for dodgy code
gcc/c-family/ChangeLog:
PR middle-end/49905
* c-ada-spec.c (dump_ada_function_declaration): Increase buffer size.
* c.opt (-Wformat-length): Add new option.
gcc/cp/ChangeLog:
PR middle-end/49905
* mangle.c (write_real_cst): Increase buffer size.
gcc/testsuite/ChangeLog:
PR middle-end/49905
* gcc.dg/format/c99-sprintf-length-1.c: New test.
* gcc.dg/format/c99-sprintf-length-2.c: New test.
* gcc.dg/format/c99-sprintf-length-opt.c: New test.
gcc/ChangeLog:
PR middle-end/49905
* Makefile.in (OBJS): Add gimple-ssa-sprintf.o.
* gimple-ssa-sprintf.c: New file.
* passes.def: Add pass_sprintf_length,
* tree-pass.h (make_pass_sprintf_length): Declare.
* doc/invoke.texi (-Wformat-length): Document.
* genmatch.c (parser::parse_expr): Increase buffer size.
* gimplify.c (gimplify_asm_expr): Same.
* passes.c (pass_manager::register_one_dump_file): Same.
* print-tree.c (print_node): Same.
@@ -1284,6 +1284,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 \
@@ -1466,7 +1466,7 @@ dump_ada_function_declaration (pretty_printer *buffer, tree func,
{
tree arg;
const tree node = TREE_TYPE (func);
- char buf[16];
+ char buf[17];
int num = 0, num_args = 0, have_args = true, have_ellipsis = false;
/* Compute number of arguments. */
@@ -458,6 +458,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 wite past the end
+of the destination region.
+
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.
@@ -482,6 +487,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 wite past the end
+of the destination region.
+
Wignored-qualifiers
C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra)
Warn whenever type qualifiers are ignored.
@@ -1622,7 +1622,9 @@ static void
write_real_cst (const tree value)
{
long target_real[4]; /* largest supported float */
- char buffer[9]; /* eight hex digits in a 32-bit number */
+ /* Buffer for eight hex digits in a 32-bit number but big enough
+ even for 64-bit long to avoid warnings. */
+ char buffer[17];
int i, limit, dir;
tree type = TREE_TYPE (value);
@@ -267,7 +267,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=2 @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
@@ -3819,6 +3820,81 @@ 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
+@opindex ffreestanding
+@opindex fno-builtin
+@opindex Wformat-length=
+
+The @option{-Wformat-length} option causes GCC to attempt to detect calls
+to formatting functions such as @code{sprintf} that might overflow the
+destination buffer, or bounded functions like @code{snprintf} that result
+in output truncation. GCC counts the number of bytes that each format
+string and directive within it writes into the provided buffer and, when
+it detects that more bytes that fit in the destination buffer may be output,
+it emits a warning. Directives whose arguments have values that can be
+determined at compile-time account for the exact number of bytes they write.
+Directives with arguments whose values cannot be determined are processed
+based on heuristics that depend on the @var{level} argument to the option,
+and on optimization. The default setting of @var{level} is 1. Level
+@var{1} 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 whose values are unknown
+are assumed to have the value of one, and strings of unknown length are
+assumed to have a length of zero. Numeric arguments that are known to
+be bounded to a subrange of their type, or string arguments whose output
+is bounded by their directive's precision, are assumed to take on the value
+within the range that results in the most bytes on output. Level @var{2}
+warns also bout calls that may overflow the destination buffer or result
+in truncation given an argument of sufficient length or magnitude. At
+this level, 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
+are assumed to be 1 character long. Enabling optimization will in most
+cases improve the accuracy of the warning, although in some cases it may
+also result in false positives.
+
+For example, at level @var{1}, 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. At level @var{2}, the call
+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 inforational note following the warning.
+
+@smallexample
+void f (int a, int b)
+@{
+ char buf [12];
+ sprintf (buf, "a = %i, b = %i\n", a, b);
+@}
+@end smallexample
+
+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 fortmat
+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 character will reduce
+the minimum buffer size. For exampe, if @var{a} and @var{b} 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
+
@item -Wno-format-zero-length
@opindex Wno-format-zero-length
@opindex Wformat-zero-length
@@ -4051,7 +4051,7 @@ parser::parse_expr ()
else if (force_capture)
{
unsigned num = capture_ids->elements ();
- char id[8];
+ char id[13]; /* Big enough for a 32-bit UINT_MAX. */
bool existed;
sprintf (id, "__%u", num);
capture_ids->get_or_insert (xstrdup (id), &existed);
@@ -486,7 +486,7 @@ make_vector_modes (enum mode_class cl, unsigned int width,
{
struct mode_data *m;
struct mode_data *v;
- char buf[8];
+ char buf[12];
unsigned int ncomponents;
enum mode_class vclass = vector_class (cl);
new file mode 100644
@@ -0,0 +1,2094 @@
+/* Copyright (C) 2016 Free Software Foundation, Inc.
+ Contributed by Martin Sebor <msebor@redhat.com>.
+
+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
+<http://www.gnu.org/licenses/>. */
+
+#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-pretty-print.h"
+#include "diagnostic-core.h"
+#include "fold-const.h"
+#include "gimple-iterator.h"
+#include "tree-ssa.h"
+#include "params.h"
+#include "tree-cfg.h"
+#include "calls.h"
+#include "cfgloop.h"
+#include "intl.h"
+
+#include "builtins.h"
+#include "stor-layout.h"
+
+const pass_data pass_data_sprintf_length = {
+ GIMPLE_PASS, // pass type
+ "sprintf_length", // 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
+};
+
+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 *);
+
+ struct call_info;
+ void compute_format_length (const call_info &);
+};
+
+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 duriing optimization. */
+ return 0 < warn_format_length && (0 < optimize) == fold_return_value;
+}
+
+struct format_result
+{
+ /* Number of characters written by the formatting function, exact,
+ minimum and maximum when an exact number cannot be determined.
+ Setting the minimum to a negative value disables all length
+ tracking for the remainder of the format string.
+ Setting either of the other two members disables the exact or
+ maximum length tracking, respectively, but continues to track
+ the maximum. */
+ int number_chars;
+ int number_chars_min;
+ int number_chars_max;
+
+ /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX
+ is the output of all directives is determined to be bounded to some
+ subrange of their types or possible lengths, false otherwise. */
+ bool bounded;
+ bool constant;
+ bool warned;
+
+ /* Location of the format string. */
+ location_t format_string_loc;
+
+ /* Increment the number of output characters by N. */
+ void inc_number_chars (int n = 1)
+ {
+ gcc_assert (0 <= n);
+ if (0 <= number_chars)
+ number_chars += n;
+ if (0 <= number_chars_min)
+ number_chars_min += n;
+ if (0 <= number_chars_max)
+ number_chars_max += n;
+ }
+};
+
+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;
+}
+
+static const char*
+get_format_string (tree format_tree, location_t *ploc)
+{
+ if (VAR_P (format_tree))
+ {
+ /* Pull out a constant value if the front end didn't. */
+ format_tree = decl_constant_value (format_tree);
+ STRIP_NOPS (format_tree);
+ }
+
+ if (integer_zerop (format_tree))
+ {
+ // FIXME: Diagnose null format string.
+ return NULL;
+ }
+
+ HOST_WIDE_INT offset = 0;
+
+ if (TREE_CODE (format_tree) == POINTER_PLUS_EXPR)
+ {
+ tree arg0 = TREE_OPERAND (format_tree, 0);
+ tree arg1 = TREE_OPERAND (format_tree, 1);
+ STRIP_NOPS (arg0);
+ STRIP_NOPS (arg1);
+
+ if (TREE_CODE (arg1) != INTEGER_CST)
+ return NULL;
+
+ format_tree = 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_tree) != ADDR_EXPR)
+ return NULL;
+
+ *ploc = EXPR_LOC_OR_LOC (format_tree, input_location);
+
+ format_tree = TREE_OPERAND (format_tree, 0);
+
+ if (TREE_CODE (format_tree) == ARRAY_REF
+ && tree_fits_shwi_p (TREE_OPERAND (format_tree, 1))
+ && (offset += tree_to_shwi (TREE_OPERAND (format_tree, 1))) >= 0)
+ format_tree = TREE_OPERAND (format_tree, 0);
+
+ if (offset < 0)
+ return NULL;
+
+ tree array_init;
+ tree array_size = NULL_TREE;
+
+ if (VAR_P (format_tree)
+ && TREE_CODE (TREE_TYPE (format_tree)) == ARRAY_TYPE
+ && (array_init = decl_constant_value (format_tree)) != format_tree
+ && 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_tree);
+ format_tree = array_init;
+ }
+
+ if (TREE_CODE (format_tree) != STRING_CST)
+ return NULL;
+
+ if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format_tree))) != char_type_node)
+ {
+ /* Wide format string. */
+ return NULL;
+ }
+
+ const char *format_chars = TREE_STRING_POINTER (format_tree);
+ unsigned format_length = TREE_STRING_LENGTH (format_tree);
+
+ 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
+ && format_length > array_size_value)
+ format_length = array_size_value;
+ }
+ }
+ if (offset)
+ {
+ if (offset >= format_length)
+ return NULL;
+
+ format_chars += offset;
+ format_length -= offset;
+ }
+
+ if (format_length < 1 || format_chars[--format_length] != 0)
+ {
+ /* Unterminated format string. */
+ return NULL;
+ }
+
+ return format_chars;
+}
+
+/* Given a string S of length LINE_WIDTH, find the visual column
+ corresponding to OFFSET bytes. */
+
+static unsigned int
+location_column_from_byte_offset (const char *s, int line_width,
+ unsigned int offset)
+{
+ const char * c = s;
+ if (*c != '"')
+ return 0;
+
+ c++, offset--;
+ while (offset > 0)
+ {
+ if (c - s >= line_width)
+ return 0;
+
+ switch (*c)
+ {
+ case '\\':
+ c++;
+ if (c - s >= line_width)
+ return 0;
+ switch (*c)
+ {
+ case '\\': case '\'': case '"': case '?':
+ case '(': case '{': case '[': case '%':
+ case 'a': case 'b': case 'f': case 'n':
+ case 'r': case 't': case 'v':
+ case 'e': case 'E':
+ c++, offset--;
+ break;
+
+ default:
+ return 0;
+ }
+ break;
+
+ case '"':
+ /* We found the end of the string too early. */
+ return 0;
+
+ default:
+ c++, offset--;
+ break;
+ }
+ }
+ return c - s;
+}
+
+/* Return a location that encodes the same location as LOC but shifted
+ by OFFSET bytes. */
+
+static location_t
+location_from_offset (location_t loc, int offset)
+{
+ gcc_checking_assert (offset >= 0);
+ if (linemap_location_from_macro_expansion_p (line_table, loc)
+ || offset < 0)
+ return loc;
+
+ expanded_location s = expand_location_to_spelling_point (loc);
+ int line_width;
+ const char *line = location_get_source_line (s.file, s.line, &line_width);
+ if (line == NULL)
+ return loc;
+ line += s.column - 1 ;
+ line_width -= s.column - 1;
+ unsigned int column =
+ location_column_from_byte_offset (line, line_width, (unsigned) offset);
+
+ return linemap_position_for_loc_and_offset (line_table, loc, column);
+}
+
+/* Return a location that encodes the same location as LOC but shifted
+ by OFFSET bytes and ending LENGTH bytes including the caret. For
+ example, to create a location for the %i directive embedded in the
+ format string below from its location, call the function with OFFSET
+ = 5 and LENGTH 3 to end up with the following:
+ sprintf ("abc-%3i-def", ...)
+ ^~~
+*/
+
+static location_t
+location_from_offset (location_t loc, int offset, int length)
+{
+ location_t beg = location_from_offset (loc, offset);
+ if (length == 0)
+ return beg;
+
+ location_t end = location_from_offset (loc, offset + length - 1);
+ return make_location (beg, beg, end);
+}
+
+/* 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
+};
+
+/* 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. */
+ int min, max;
+ /* True when the range is the result of an argument determined
+ to be bounded to a subrange of its type, false otherwise. */
+ bool bounded;
+ bool constant;
+};
+
+/* Description of a conversion specification. */
+
+struct conversion_spec
+{
+ /* A bitmap of flags, one for each character. */
+ int flags[256 / sizeof (int)];
+ int width; /* Numeric width. */
+ int precision; /* Numeric precision. */
+
+ tree star_width; /* Width specified via the '*' character. */
+ tree star_precision; /* Precision specified via the asterisk. */
+
+ format_lengths modifier; /* Length modifier. */
+ char specifier; /* Format specifier character. */
+
+ unsigned have_width: 1; /* Numeric width was given. */
+ unsigned have_precision: 1; /* Numeric precision was given. */
+
+ 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)]
+ & (1 << (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)]
+ |= (1 << (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, return the number
+ of bytes remaining in the destination buffer whose size is OBJSIZE. */
+
+static inline size_t
+bytes_remaining (size_t navail,
+ const format_result &res)
+{
+ 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 (0 <= res.number_chars_max)
+ navail -= res.number_chars_max;
+ else if (0 <= res.number_chars)
+ navail -= res.number_chars;
+ else if (0 <= res.number_chars_min)
+ navail -= res.number_chars_min;
+ }
+ else
+ {
+ /* At level 1 use the smaller of the byte counters to compute
+ the result. */
+ if (0 <= res.number_chars)
+ navail -= res.number_chars;
+ else if (0 <= res.number_chars_min)
+ navail -= res.number_chars_min;
+ else if (0 <= res.number_chars_max)
+ navail -= res.number_chars_max;
+ }
+
+ return navail;
+}
+
+
+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. */
+ tree format;
+ const char *fmtstr;
+
+ /* The location of the format argument. */
+ location_t fmtloc;
+
+ /* The destination size specified for bounded functions such
+ as snprintf, -1 for others. */
+ // unsigned HOST_WIDE_INT size;
+
+ /* 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;
+
+ 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.min = res.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. */
+
+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;
+ }
+ }
+ }
+}
+
+/* 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
+ midifier. */
+ 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 = 10;
+
+ /* True when a signed conversion is preceded by a sign or space. */
+ bool maybesign = false;
+
+ /* True when a conversion is preceded by a prefix indicating the base
+ of the argument (octal or hexadecimal). */
+ bool maybebase = false;
+
+ switch (spec.specifier)
+ {
+ case 'd':
+ case 'i':
+ /* Space is only effective for signed conversions. */
+ maybesign = spec.get_flag (' ');
+ case 'u':
+ break;
+ case 'o':
+ base = 8;
+ break;
+
+ case 'p':
+#if TARGET_AIX
+ /* AIX "%p" is the same as "%x", with '#' being ignored. */
+#elif defined OPTION_GLIBC
+ /* GLIBC "%p" is the same as "%#x" except that null pointers
+ convert to "(nil)". */
+ if (integer_zerop (arg))
+ {
+ res.min = res.max = 5;
+ res.bounded = true;
+ return res;
+ }
+ maybebase = true;
+
+ /* GLIBC also respects the space flag. */
+ maybesign = spec.get_flag (' ');
+
+#elif TARGET_SOLARIS
+ /* Solaris "%p" is the same as "%x" with the '#' flag having
+ the same function. */
+ maybebase = spec.get_flag ('#');
+#else
+ /* Conservatively assume that all other implementations format
+ pointers in hexadecimal with no base prefix at a minimum,
+ and with it as a maximum. */
+ res.min = tree_digits (arg, 16, false, false);
+ res.max = res.min + 2;
+ res.bounded = false;
+ return res;
+#endif
+ base = 16;
+ break;
+
+ case 'X':
+ case 'x':
+ base = 16;
+ break;
+ default:
+ gcc_unreachable ();
+ }
+
+ /* Convert the argument to the type of the directive. */
+ arg = fold_convert (dirtype, arg);
+
+ maybesign |= spec.get_flag ('+');
+ maybebase |= spec.get_flag ('#');
+ int len = tree_digits (arg, base, maybesign, maybebase);
+
+ if (len < prec)
+ len = prec;
+
+ if (len < width)
+ len = width;
+
+ res.max = len;
+ res.min = res.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.min = res.max = -1;
+ 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 larger 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).min;
+ int maxbytes = format_integer (spec, res.argmax).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 the Value Range Propagationa. */
+ res.bounded = true;
+ }
+ else if (range_type == VR_ANTI_RANGE)
+ {
+ /* Handle anti-ranges if/when bug 71690 is ever 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.min = format_integer (spec, argmin).min;
+ res.max = format_integer (spec, argmax).max;
+ if (res.max < res.min)
+ {
+ int tmp = res.max;
+ res.max = res.min;
+ res.min = tmp;
+ }
+ 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)
+{
+ /* 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 : -1;
+
+ 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;
+
+ tree type = arg ? TREE_TYPE (arg) : NULL_TREE;
+
+ switch (spec.modifier)
+ {
+ case FMT_LEN_none:
+ if (!type)
+ type = double_type_node;
+ break;
+
+ case FMT_LEN_L:
+ case FMT_LEN_ll:
+ if (!type)
+ type = long_double_type_node;
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ /* The minimum and maximum number of bytes produced by the directive. */
+ fmtresult res = fmtresult ();
+
+ res.constant = false;
+
+ /* Number of exponent digits or -1 when unknown. */
+ int expdigs = -1;
+ /* 1 when ARG < 0, 0 when ARG >= 0, -1 when unknown. */
+ int negative = -1;
+ /* Log10 of EXPDIGS. */
+ int logexpdigs = 2;
+
+ const double log10_2 = .30102999566398119521;
+
+ if (arg && TREE_CODE (arg) == REAL_CST)
+ {
+ expdigs = real_exponent (TREE_REAL_CST_PTR (arg)) * log10_2;
+ negative = real_isneg (TREE_REAL_CST_PTR (arg));
+ logexpdigs = ilog (expdigs, 10);
+ }
+ else if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
+ {
+ /* Compute T_MAX_EXP for base 2. */
+ expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;
+ }
+
+
+ switch (spec.specifier)
+ {
+ case 'A':
+ case 'a':
+ /* The minimum output is "0x.p+0". */
+ res.min = 6 + (0 < prec ? prec : 0);
+ /* FIXME: Figure out the maximum. */
+ res.max = -1;
+ if (res.min < width)
+ res.min = width;
+ break;
+
+ case 'E':
+ case 'e':
+ /* The minimum output is "[-+]1.234567e+00" for an IEEE double
+ regardless of the value of the actual argument. */
+ res.min = ((0 < negative || spec.get_flag ('+') || spec.get_flag (' '))
+ + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+ + 2 /* e+ */ + (logexpdigs < 2 ? 2 : logexpdigs));
+ /* The maximum output is "-1.234567e+123" for a double and one
+ more byte for a large exponent for a long louble. */
+ res.max = negative < 0 ? res.min + 2 + (spec.get_flag ('L')) : res.min;
+ if (res.min < width)
+ res.min = width;
+ if (res.max < width)
+ res.max = width;
+ break;
+
+ case 'F':
+ case 'f':
+ /* The minimum output is "1.234567" regardless of the value
+ of the actual argument. */
+ res.min = 2 + (prec < 0 ? 6 : prec);
+ /* The maximum depends on the magnitude of the value but it's
+ at most 316 bytes for double and 4940 for long double, plus
+ precision if non-negative, or 6. */
+ res.max = expdigs + (prec < 0 ? 6 : prec ? prec + 1 : 0);
+ break;
+
+ case 'G':
+ case 'g':
+ /* Treat this the same as '%F' for now even though that's
+ inaccurate. */
+ res.min = 2 + (prec < 0 ? 6 : prec);
+ res.max = ((spec.get_flag ('L') ? 4934 : 310)
+ + (prec < 0 ? 6 : prec ? prec + 1 : 0));
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ /* The argument is only considered bounded when the range of output
+ bytes is exact. */
+ res.bounded = res.min == res.max;
+ return res;
+}
+
+/* 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.min = -1;
+ res.max = -1;
+ 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.min = res.max = tree_to_shwi (slen);
+ res.bounded = true;
+ res.constant = true;
+ return res;
+ }
+
+ if (TREE_CODE (str) == ADDR_EXPR)
+ return get_string_length (TREE_OPERAND (str, 0));
+
+ if (TREE_CODE (str) == COMPONENT_REF
+ && TREE_CODE (TREE_TYPE (TREE_OPERAND (str, 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. */
+ fmtresult res;
+ if (tree arraysize = TYPE_SIZE_UNIT (TREE_TYPE (TREE_OPERAND (str, 1))))
+ {
+ res.min = 0;
+ res.max = tree_to_uhwi (arraysize) - 1;
+ }
+ else
+ res.min = res.max = -1;
+
+ /* Using the type of the character array to determine the maximum
+ length of output is not considered bounded. */
+ res.bounded = false;
+ res.constant = false;
+ return res;
+ }
+
+ if (TREE_CODE (str) == SSA_NAME)
+ {
+ gimple *def = SSA_NAME_DEF_STMT (str);
+ enum gimple_code code = gimple_code (def);
+
+ if (code == GIMPLE_ASSIGN)
+ {
+ tree rhs = gimple_assign_rhs1 (def);
+ return get_string_length (rhs);
+ }
+ else if (code == GIMPLE_PHI)
+ {
+ /* Try to determine the longest and shortest string the argument
+ refers to in an attempt to handle buffer overflow in common
+ cases such as:
+ char d [8];
+ sprintf (d, "[%s]", i ? "abcdef", "xyz");
+ */
+ fmtresult res = fmtresult ();
+ res.min = INT_MAX;
+ res.max = 0;
+
+ for (unsigned i = 0; i < gimple_phi_num_args (def); ++i)
+ {
+ tree arg = gimple_phi_arg (def, i)->def;
+ fmtresult tmp = get_string_length (arg);
+ if (tmp.min < res.min && 0 <= tmp.min)
+ res.min = tmp.min;
+ if (res.max < tmp.max || tmp.max < 0)
+ res.max = tmp.max;
+
+ res.bounded |= tmp.bounded;
+ }
+
+ if (res.min == INT_MAX)
+ res.min = -1;
+
+ 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. */
+
+static fmtresult
+format_string (const conversion_spec &spec, tree arg)
+{
+ int width = spec.have_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 (spec.star_precision)
+ prec = (TREE_CODE (spec.star_precision) == INTEGER_CST)
+ ? tree_to_shwi (spec.star_precision) : -1;
+
+ fmtresult res = fmtresult ();
+
+ /* The argument is likely unbounded (i.e., its length is likely
+ unknown. */
+ res.bounded = false;
+ res.constant = false;
+
+ /* The number of bytes formatted. This applies to both '%s' and
+ '%ls' where precision and width are in converted characters
+ (i.e., bytes). */
+ int nbytes;
+
+ if (spec.specifier == 'c')
+ {
+ if (spec.modifier == FMT_LEN_l)
+ {
+ 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.min = 0 < width ? width : 1 < warn_format_length ? nul < 1: !nul;
+ res.max = -1;
+ return res;
+ }
+
+ /* A plain '%c' directive. */
+ nbytes = 1;
+ res.constant = arg && TREE_CODE (arg) == INTEGER_CST;
+ }
+ else
+ {
+ fmtresult slen = get_string_length (arg);
+ if (slen.constant)
+ {
+ gcc_checking_assert (slen.min == slen.max);
+
+ /* A '%s' directive with a string argument with constant length. */
+ nbytes = slen.min;
+ if (0 <= prec && prec < nbytes)
+ nbytes = prec;
+
+ if (spec.modifier == FMT_LEN_l)
+ {
+ /* For a '%ls' directive the minimum number of bytes is
+ the greater of WIDTH and the string length, and the
+ maximum is either PRECISION when specified or
+ MB_CUR_MAX * length, which is unknown, so set it
+ to -1. */
+ res.min = nbytes < width ? width : nbytes;
+ /* 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.max = 0 <= prec ? prec : -1;
+
+ res.bounded = -1 < res.max;
+ return res;
+ }
+
+ res.constant = true;
+ }
+ 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 (prec < slen.min || slen.min < 0)
+ slen.min = prec;
+ if (prec < slen.max || slen.max < 0)
+ slen.max = prec;
+ }
+ else if (slen.min < 0)
+ slen.min = width ? width : 1 < warn_format_length;
+
+ res.min = slen.min;
+ res.max = slen.max;
+
+ /* 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;
+ return res;
+ }
+ }
+
+ if (nbytes < width)
+ nbytes = width;
+
+ res.min = res.max = nbytes;
+
+ /* The length is exact. */
+ res.bounded = true;
+
+ return res;
+}
+
+static void
+compute_format_length (const pass_sprintf_length::call_info &info,
+ format_result *res,
+ const char *cvtbeg,
+ size_t cvtlen,
+ size_t offset,
+ const conversion_spec &spec,
+ tree arg)
+{
+ /* Create a location for the whole directive from the % to the format
+ specifier. */
+ location_t dirloc = location_from_offset (info.fmtloc, offset + 1, cvtlen);
+
+ /* Bail when there is no function to compute the output length,
+ or when the size of the object is too big (i.e., unknown)
+ or when minimum length checking has been disabled. */
+ if (!spec.fmtfunc
+ || HOST_WIDE_INT_MAX <= info.objsize
+ || res->number_chars_min == -1)
+ 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.max < 0)
+ {
+ /* 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 = -1;
+ res->number_chars = -1;
+ }
+
+ if (fmtres.min < 0)
+ {
+ /* 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 = -1;
+ 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 = bytes_remaining (info.objsize, *res);
+
+ /* The destination will have already overflowed if the number of
+ bytes has wrapped around zero. */
+ bool overflowed = HOST_WIDE_INT_MAX <= navail;
+
+ if (fmtres.min < fmtres.max)
+ {
+ /* The result is a range (i.e., it's inexact). */
+ if (!overflowed)
+ {
+ bool warned = false;
+
+ if (navail < (size_t)fmtres.min)
+ {
+ if (fmtres.min == fmtres.max)
+ {
+ const char* fmtstr
+ = (info.bounded
+ ? G_("%<%.*s%> directive output truncated writing "
+ "%i bytes into a region of size %wu")
+ : G_("%<%.*s%> directive writing %i bytes "
+ "into a region of size %wu"));
+ warned = warning_at (dirloc, OPT_Wformat_length_, fmtstr,
+ (int)cvtlen, cvtbeg, fmtres.min,
+ navail);
+ }
+ else
+ {
+ const char* fmtstr
+ = (info.bounded
+ ? G_("%<%.*s%> directive output truncated writing "
+ "between %i and %i bytes into a region of size %wu")
+ : G_("%<%.*s%> directive writing between %i and %i bytes "
+ "into a region of size %wu"));
+ warned = warning_at (dirloc, OPT_Wformat_length_, fmtstr,
+ (int)cvtlen, cvtbeg,
+ fmtres.min, fmtres.max, navail);
+ }
+ }
+ else if (navail < (unsigned)fmtres.max
+ && (fmtres.bounded || 1 < warn_format_length))
+ {
+ const char* fmtstr
+ = (info.bounded
+ ? G_("%<%.*s%> directive output may be truncated writing "
+ "between %i and %i bytes into a region of size %wu")
+ : G_("%<%.*s%> directive writing between %i and %i bytes "
+ "into a region of size %wu"));
+ warned = warning_at (dirloc, OPT_Wformat_length_, fmtstr,
+ (int)cvtlen, cvtbeg,
+ fmtres.min, fmtres.max, navail);
+ }
+
+ res->warned |= warned;
+
+ if (warned && fmtres.argmin)
+ {
+ if (fmtres.argmin == fmtres.argmax)
+ inform (dirloc, "directive argument %qE", fmtres.argmin);
+ else if (fmtres.bounded)
+ inform (dirloc, "directive argument in the range [%qE, %qE]",
+ fmtres.argmin, fmtres.argmax);
+ else
+ inform (dirloc,
+ "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 = -1;
+ if (res->number_chars_max != -1 && fmtres.max != -1)
+ res->number_chars_max += fmtres.max;
+
+ res->number_chars_min += fmtres.min;
+ }
+ else
+ {
+ if (!overflowed && 0 < fmtres.min && navail < (unsigned)fmtres.min)
+ {
+ const char* fmtstr
+ = (info.bounded
+ ? (1 < fmtres.min
+ ? G_("%<%.*s%> directive output truncated while writing "
+ "%i bytes into a region of size %wu")
+ : G_("%<%.*s%> directive output truncated while writing "
+ "%i byte into a region of size %wu"))
+ : (1 < fmtres.min
+ ? G_("%<%.*s%> directive writing %i bytes "
+ "into a region of size %wu")
+ : G_("%<%.*s%> directive writing %i byte "
+ "into a region of size %wu")));
+
+ res->warned = warning_at (dirloc, OPT_Wformat_length_, fmtstr,
+ (int)cvtlen, cvtbeg, fmtres.min, navail);
+ }
+ res->inc_number_chars (fmtres.min);
+ }
+}
+
+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 < 0)
+ 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 number 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. */
+ unsigned HOST_WIDE_INT navail = bytes_remaining (info.objsize, *res);
+
+ /* If the number of available bytes has wrapped around zero
+ the destination has already overflowed and been diagnosed so
+ avoid diagnosing it again. In the diagnostic, distinguish between
+ a possible overflow ("may write") and a certain overflow somewhere
+ "past the end." (Ditto for truncation.) */
+ bool overflowed = HOST_WIDE_INT_MAX <= navail;
+ if (!overflowed && navail < nbytes)
+ {
+ /* 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);
+
+ /* Create a location range from the last processed format character
+ to the current one (or the end of the format string). */
+ location_t loc = location_from_offset (info.fmtloc, off + 1, 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
+ && (unsigned)res->number_chars_min < info.objsize);
+
+ res->warned = true;
+
+ if (!end && (nbytes - navail) == 1)
+ {
+ /* There is room the rest of the format string but none
+ for the terminating nul. */
+ const char *text
+ = (info.bounded // Snprintf an 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")));
+
+ warning_at (loc, 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 < 0
+ ? 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")));
+
+ warning_at (loc, OPT_Wformat_length_, text, info.fmtstr[off], off);
+ }
+ }
+
+ /* if (warn_format_length */
+ /* && (overflowed || navail < nbytes */
+ /* || (1 < warn_format_length && )) */
+ if (res->warned)
+ {
+ /* Help the user figure out how big a buffer they need. */
+
+ location_t callloc = gimple_location (info.callstmt);
+
+ unsigned HOST_WIDE_INT min
+ = -1 < res->number_chars_min ? res->number_chars_min : -1;
+
+ unsigned HOST_WIDE_INT max
+ = -1 < res->number_chars_max ? res->number_chars_max : -1;
+
+ unsigned HOST_WIDE_INT exact
+ = -1 < res->number_chars ? res->number_chars : res->number_chars_min;
+
+ if (min < max && max != HOST_WIDE_INT_M1U)
+ 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
+ ? "format output %wu byte into a destination of size %wu"
+ : "format output %wu bytes into a destination of size %wu"),
+ nbytes + exact, info.objsize);
+ }
+
+ res->inc_number_chars (nbytes);
+}
+
+void
+pass_sprintf_length::compute_format_length (const call_info &info)
+{
+ /* Bail early if format length checking is disabled, either
+ via a command line option, or as a result of the size of
+ the destination object not being available, or due to
+ format directives or arguments encountered during processing
+ that prevent length tracking with any reliability. */
+
+ if (HOST_WIDE_INT_MAX <= info.objsize)
+ return;
+
+ /* The variadic argument counter. */
+ unsigned argno = info.argidx;
+
+ /* The aggregate result for the call. */
+ format_result res = format_result ();
+
+ /* No directive has been seen yet so the output is bounded and constant
+ until determined otherwise. */
+ res.bounded = true;
+ res.constant = true;
+
+ 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 (*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. */
+ spec.star_width = gimple_call_arg (info.callstmt, argno++);
+ ++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)
+ {
+ case '%':
+ spec.fmtfunc = format_percent;
+ break;
+
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ spec.fmtfunc = format_floating;
+ break;
+
+ case 'd':
+ case 'i':
+ case 'o':
+ case 'p':
+ case 'u':
+ case 'x':
+ case 'X':
+ spec.fmtfunc = format_integer;
+ 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;
+
+ /* Offset of the beginning of the directive from the beginning
+ of the format string. */
+ size_t diroff = dir - info.fmtstr;
+
+ /* Extract the argument (if the directive takes one). */
+ tree arg
+ = (spec.specifier == '%'
+ ? NULL_TREE : gimple_call_arg (info.callstmt,
+ dollar ? dollar : argno++));
+
+ ::compute_format_length (info, &res, dir, dirlen, diroff, spec, arg);
+ }
+}
+
+/* Return the size of the object referenced by the expression DEST.
+ Try to work around some of the limitations of __builtin_object_size
+ in the common simple case when DEST is a POINTER_PLUS_EXPR involving
+ an array. */
+
+static unsigned HOST_WIDE_INT
+get_destination_size (tree dest)
+{
+ /* Try to use __builtin_object_size although it rarely returns
+ a useful result even for straighforward cases. */
+ tree ost = warn_format_length < 2
+ ? integer_zero_node : build_int_cst (size_type_node, 2);
+ tree args[] = { dest, ost };
+ tree func = builtin_decl_explicit (BUILT_IN_OBJECT_SIZE);
+ if (tree size = fold_builtin_n (UNKNOWN_LOCATION, func, args, 2, false))
+ return tree_to_uhwi (STRIP_NOPS (size));
+
+ /* If __builtin_object_size fails to deliver, try to compute
+ it for the very basic (but common) cases. */
+ if (TREE_CODE (dest) == SSA_NAME
+ && POINTER_TYPE_P (TREE_TYPE (dest)))
+ {
+ gimple *def = SSA_NAME_DEF_STMT (dest);
+ if (gimple_code (def) == GIMPLE_ASSIGN)
+ {
+ tree_code code = gimple_assign_rhs_code (def);
+ if (code == POINTER_PLUS_EXPR)
+ {
+ tree off = gimple_assign_rhs2 (def);
+ dest = gimple_assign_rhs1 (def);
+
+ if (cst_and_fits_in_hwi (off))
+ {
+ unsigned HOST_WIDE_INT size = get_destination_size (dest);
+ if (size != HOST_WIDE_INT_M1U)
+ return size - tree_to_shwi (off);
+ }
+ }
+ }
+ }
+
+ return -1;
+}
+
+/* Determine if a GIMPLE CALL is one to one of the sprintf-like built-in
+ functions and if so, handle it. */
+
+void
+pass_sprintf_length::handle_gimple_call (gimple *call)
+{
+ call_info info = call_info ();
+
+ info.callstmt = call;
+ 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)
+ return;
+
+ info.fncode = DECL_FUNCTION_CODE (info.func);
+
+ /* The size of the destination as in snprintf(dest, size, ...). */
+ unsigned HOST_WIDE_INT dstsize = ~(unsigned HOST_WIDE_INT)0;
+
+ /* The size of the destination determined by __builtin_object_size. */
+ unsigned HOST_WIDE_INT objsize = ~(unsigned HOST_WIDE_INT)0;
+
+ /* Buffer size argument number (snprintf and vsnprintf). */
+ unsigned idx_dstsize = -1;
+
+ /* Object size argument number (snprintf_chk and vsnprintf_chk). */
+ unsigned idx_objsize = -1;
+
+ /* 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 == -1U)
+ {
+ // 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);
+ if (dstsize > HOST_WIDE_INT_MAX)
+ warning_at (gimple_location (info.callstmt), 0,
+ "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 != -1U)
+ {
+ 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, bounded function allow the explicitly
+ specified destination size argument to be zero as a request
+ to determine the number of bytes on output without actually
+ writing any output. Disable length checking for those. */
+ info.objsize = HOST_WIDE_INT_M1U;
+ }
+ 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), 0,
+ "specified size %wu exceeds the size %wu "
+ "of the destination object", dstsize, objsize);
+ }
+ }
+
+ if (!info.format)
+ {
+ // Should this give a warning?
+ return;
+ }
+
+ info.fmtstr = get_format_string (info.format, &info.fmtloc);
+ if (!info.fmtstr)
+ return;
+
+ compute_format_length (info);
+}
+
+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 (stmt);
+ }
+ }
+
+ return 0;
+}
+
+gimple_opt_pass *
+make_pass_sprintf_length (gcc::context *ctxt)
+{
+ return new pass_sprintf_length (ctxt);
+}
@@ -5346,7 +5346,7 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
flexibility, split it into separate input and output
operands. */
tree input;
- char buf[10];
+ char buf[11]; /* Big enough for a 32-bit UINT_MAX. */
/* Turn the in/out constraint into an output constraint. */
char *p = xstrdup (constraint);
@@ -5356,7 +5356,7 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
/* And add a matching input constraint. */
if (allows_reg)
{
- sprintf (buf, "%d", i);
+ sprintf (buf, "%u", i);
/* If there are multiple alternatives in the constraint,
handle each of them individually. Those that allow register
/* Set the DECL_ASSEMBLER_NAME for DECL. */
void
lhd_set_decl_assembler_name (tree decl)
@@ -771,7 +771,7 @@ pass_manager::register_one_dump_file (opt_pass *pass)
{
char *dot_name, *flag_name, *glob_name;
const char *name, *full_name, *prefix;
- char num[10];
+ char num[11]; /* Big enough for a 32-bit UINT_MAX. */
int flags, id;
int optgroup_flags = OPTGROUP_NONE;
gcc::dump_manager *dumps = m_ctxt->get_dumps ();
@@ -779,7 +779,7 @@ pass_manager::register_one_dump_file (opt_pass *pass)
/* See below in next_pass_1. */
num[0] = '\0';
if (pass->static_pass_number != -1)
- sprintf (num, "%d", ((int) pass->static_pass_number < 0
+ sprintf (num, "%u", ((int) pass->static_pass_number < 0
? 1 : pass->static_pass_number));
/* The name is both used to identify the pass for the purposes of plugins,
@@ -2425,8 +2425,15 @@ execute_pass_list_1 (opt_pass *pass)
if (cfun == NULL)
return;
+
+ // inform (0, "executing pass: %s", pass->name);
+
if (execute_one_pass (pass) && pass->sub)
- execute_pass_list_1 (pass->sub);
+ {
+ // inform (0, "executing subpass: %s", pass->sub->name);
+ execute_pass_list_1 (pass->sub);
+ }
+
pass = pass->next;
}
while (pass);
@@ -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. */
@@ -303,6 +304,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);
@@ -694,8 +694,8 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
i = 0;
FOR_EACH_CALL_EXPR_ARG (arg, iter, node)
{
- char temp[10];
- sprintf (temp, "arg %d", i);
+ char temp[15]; /* Big enough for a 32-bit UINT_MAX. */
+ sprintf (temp, "arg %u", i);
print_node (file, temp, arg, indent + 4);
i++;
}
@@ -706,7 +706,7 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
for (i = 0; i < len; i++)
{
- char temp[10];
+ char temp[15]; /* Big enough for a 32-bit UINT_MAX. */
sprintf (temp, "arg %d", i);
print_node (file, temp, TREE_OPERAND (node, i), indent + 4);
@@ -814,7 +814,7 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
for (i = 0; i < len; i++)
if (TREE_VEC_ELT (node, i))
{
- char temp[10];
+ char temp[15]; /* Big enough for a 32-bit UINT_MAX. */
sprintf (temp, "elt %d", i);
print_node (file, temp, TREE_VEC_ELT (node, i), indent + 4);
}
new file mode 100644
@@ -0,0 +1,1295 @@
+/* { 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
+
+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;
+
+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);
+
+/* Exercise the "%c" and "%lc" directive with constant arguments. */
+
+void test_sprintf_c_const (void)
+{
+ 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');
+}
+
+/* Exercise the "%p" directive with constant arguments. */
+
+void test_sprintf_p_const (void)
+{
+ /* The exact output for %p is unspecified by C. It's based on GLIBC
+ output */
+ T (0, "%p", (void*)0x1); /* { dg-warning ".%p. directive writing 3 bytes into a region of size 0" } */
+ T (1, "%p", (void*)0x12); /* { dg-warning ".%p. directive writing 4 bytes into a region of size 1" } */
+ T (2, "%p", (void*)0x123); /* { dg-warning ".%p. directive writing 5 bytes into a region of size 2" } */
+
+ /* GLIBC treats the ' ' flag with the "%p" directive the same as with
+ signed integer conversions (i.e., it prepends a space). */
+ T (5, "% p", (void*)0x234); /* { dg-warning ". . flag used with .%p.|.% p. directive writing 6 bytes into a region of size 5" } */
+}
+
+/* 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);
+}
+
+/* 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" } */
+
+ P (buffer (6), /* { dg-message "format output 7 bytes into a destination of size 6" } */
+ "%c%s%i", '1', "2", 3456); /* { dg-warning "13: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__)
+
+/* Exercise the "%c" and "%lc" directive with constant arguments. */
+
+void test_sprintf_chk_c_const (void)
+{
+ /* 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 (0, "%*s", 0, ""); /* { 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, "%*s", 1, ""); /* { dg-warning "nul past the end" } */
+ T (1, "%*s", 1, s0); /* { dg-warning "nul past the end" } */
+
+ 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" } */
+
+ /* 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 (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); /* { 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); /* { 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); /* { 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 (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);
+}
+
+/* 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 (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 (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 (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 (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)
+{
+ /* 1.0 is formatted as "1.000000E+00" (i.e., 12 bytes). */
+ 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);
+
+ /* 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 (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)
+{
+ /* 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 (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)
+{
+ /* 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 (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 (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 (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 (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" } */
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,195 @@
+/* { 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];
+};
+
+/* 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);
+ T (1, "%-s", a->a2); /* { dg-warning "may write a terminating nul" } */
+ T (1, "%-s", a->a3); /* { dg-warning "writing between 0 and 2 bytes into a region of size 1" } */
+}
+
+ /* 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);
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,223 @@
+/* { 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) \
+ ((!LINE || __LINE__ == LINE) ? __builtin_object_size (x, 0) : __SIZE_MAX__ / 2)
+
+#define T(bufsize, fmt, ...) \
+ do { \
+ 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;
+}
+
+static int idx;
+
+/* 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)
+{
+ /* 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)
+{
+#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)
+{
+#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" } */
+}
@@ -469,6 +469,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);