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