PR middle-end/49905 - Better sanity checking on sprintf src & dest to
produce warning for dodgy code ?
gcc/ChangeLog:
PR middle-end/49905
* config/linux.h (TARGET_LIBC_PRINTF_POINTER_FORMAT): Redefine.
* config/sol2.h (TARGET_LIBC_PRINTF_POINTER_FORMAT): Same.
* doc/invoke.texi (-Wformat-length, -fprintf-return-value): New
options.
* doc/tm.texi.in (TARGET_LIBC_PRINTF_ROUND_MODE): Document.
(TARGET_LIBC_PRINTF_ROUND_MODE): Same.
* 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: Correct/rework floating point. Implement
optimization.
* passes.def (pass_sprintf_length): Add new pass.
* genmodes.c (make_vector_modes): Increase buffer size.
* gimplify.c (gimplify_asm_expr): Same.
* passes.c (pass_manager::register_one_dump_file): Same.
* print-tree.c (print_node): Same.
* targhooks.h (default_libc_printf_round_mode): Declare new function.
(default_libc_printf_pointer_format): Same.
(gnu_libc_printf_pointer_format): Same.
(solaris_libc_printf_pointer_format): Same.
* targhooks.c (default_libc_printf_round_mode): Define new function.
(default_libc_printf_pointer_format): Same.
(gnu_libc_printf_pointer_format): Same.
(solaris_libc_printf_pointer_format): Same.
gcc/c-family/ChangeLog:
PR middle-end/49905
* c.opt: Add -Wformat-length and -fprintf-return-value.
gcc/cp/ChangeLog:
PR middle-end/49905
* mangle.c (write_real_cst): Increase buffer size.
gcc/go/ChangeLog:
PR middle-end/49905
* gofrontend/expressions.cc (Call_expression::do_flatten): Increase
buffer size.
gcc/testsuite/ChangeLog:
PR middle-end/49905
* 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.c: New test.
@@ -1296,6 +1296,7 @@ OBJS = \
gimple-ssa-nonnull-compare.o \
gimple-ssa-split-paths.o \
gimple-ssa-strength-reduction.o \
+ gimple-ssa-sprintf.o \
gimple-streamer-in.o \
gimple-streamer-out.o \
gimple-walk.o \
@@ -1466,7 +1466,7 @@ dump_ada_function_declaration (pretty_printer *buffer, tree func,
{
tree arg;
const tree node = TREE_TYPE (func);
- char buf[16];
+ char buf[17];
int num = 0, num_args = 0, have_args = true, have_ellipsis = false;
/* Compute number of arguments. */
@@ -458,6 +458,11 @@ Wformat-extra-args
C ObjC C++ ObjC++ Var(warn_format_extra_args) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
Warn if passing too many arguments to a function for its format string.
+Wformat-length
+C ObjC C++ ObjC++ Warning Alias(Wformat-length=, 1, 0)
+Warn about function calls with format strings that wite past the end
+of the destination region. 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.
@@ -482,6 +487,11 @@ Wformat=
C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 1, 0)
Warn about printf/scanf/strftime/strfmon format string anomalies.
+Wformat-length=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format_length) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
+Warn about function calls with format strings that wite past the end
+of the destination region.
+
Wignored-qualifiers
C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra)
Warn whenever type qualifiers are ignored.
@@ -1455,6 +1465,10 @@ fpretty-templates
C++ ObjC++ Var(flag_pretty_templates) Init(1)
-fno-pretty-templates Do not pretty-print template specializations as the template signature followed by the arguments.
+fprintf-return-value
+C ObjC C++ ObjC++ LTO Optimization Var(flag_printf_return_value) Init(0)
+Treat known printf 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.
@@ -208,3 +208,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
# define TARGET_LIBC_HAS_FUNCTION linux_libc_has_function
#endif
+
+/* The format string to which "%p" corresponds. */
+#undef TARGET_LIBC_PRINTF_POINTER_FORMAT
+#define TARGET_LIBC_PRINTF_POINTER_FORMAT gnu_libc_printf_pointer_format
@@ -440,6 +440,10 @@ along with GCC; see the file COPYING3. If not see
#undef TARGET_LIBC_HAS_FUNCTION
#define TARGET_LIBC_HAS_FUNCTION default_libc_has_function
+/* The format string to which "%p" corresponds. */
+#undef TARGET_LIBC_PRINTF_POINTER_FORMAT
+#define TARGET_LIBC_PRINTF_POINTER_FORMAT solaris_libc_printf_pointer_format
+
extern GTY(()) tree solaris_pending_aligns;
extern GTY(()) tree solaris_pending_inits;
extern GTY(()) tree solaris_pending_finis;
@@ -1740,7 +1740,9 @@ static void
write_real_cst (const tree value)
{
long target_real[4]; /* largest supported float */
- char buffer[9]; /* eight hex digits in a 32-bit number */
+ /* Buffer for eight hex digits in a 32-bit number but big enough
+ even for 64-bit long to avoid warnings. */
+ char buffer[17];
int i, limit, dir;
tree type = TREE_TYPE (value);
@@ -268,7 +268,8 @@ Objective-C and Objective-C++ Dialects}.
-Wno-div-by-zero -Wdouble-promotion -Wduplicated-cond @gol
-Wempty-body -Wenum-compare -Wno-endif-labels @gol
-Werror -Werror=* -Wfatal-errors -Wfloat-equal -Wformat -Wformat=2 @gol
--Wno-format-contains-nul -Wno-format-extra-args -Wformat-nonliteral @gol
+-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=1 @gol
+-Wformat-nonliteral @gol
-Wformat-security -Wformat-signedness -Wformat-y2k -Wframe-address @gol
-Wframe-larger-than=@var{len} -Wno-free-nonheap-object -Wjump-misses-init @gol
-Wignored-qualifiers -Wignored-attributes -Wincompatible-pointer-types @gol
@@ -379,7 +380,7 @@ Objective-C and Objective-C++ Dialects}.
-fno-toplevel-reorder -fno-trapping-math -fno-zero-initialized-in-bss @gol
-fomit-frame-pointer -foptimize-sibling-calls @gol
-fpartial-inlining -fpeel-loops -fpredictive-commoning @gol
--fprefetch-loop-arrays @gol
+-fprefetch-loop-arrays -fprintf-return-value @gol
-fprofile-correction @gol
-fprofile-use -fprofile-use=@var{path} -fprofile-values @gol
-fprofile-reorder-functions @gol
@@ -3825,6 +3826,155 @@ in the case of @code{scanf} formats, this option suppresses the
warning if the unused arguments are all pointers, since the Single
Unix Specification says that such unused arguments are allowed.
+@item -Wformat-length
+@itemx -Wformat-length=@var{level}
+@opindex Wformat-length
+@opindex Wno-format-length
+@opindex ffreestanding
+@opindex fno-builtin
+@opindex Wformat-length=
+
+The @option{-Wformat-length} option causes GCC to attempt to detect calls
+to formatting functions such as @code{sprintf} that might overflow the
+destination buffer, or bounded functions like @code{snprintf} that result
+in output truncation. GCC counts the number of bytes that each format
+string and directive within it writes into the provided buffer and, when
+it detects that more bytes that fit in the destination buffer may be output,
+it emits a warning. Directives whose arguments have values that can be
+determined at compile-time account for the exact number of bytes they write.
+Directives with arguments whose values cannot be determined are processed
+based on heuristics that depend on the @var{level} argument to the option,
+and on optimization. The default setting of @var{level} is 1. Level
+@var{1} employs a conservative approach that warns only about calls that
+most likely overflow the buffer or result in output truncation. At this
+level, numeric arguments to format directives whose values are unknown
+are assumed to have the value of one, and strings of unknown length are
+assumed to have a length of zero. Numeric arguments that are known to
+be bounded to a subrange of their type, or string arguments whose output
+is bounded by their directive's precision, are assumed to take on the value
+within the range that results in the most bytes on output. Level @var{2}
+warns also bout calls that may overflow the destination buffer or result
+in truncation given an argument of sufficient length or magnitude. At
+this level, unknown numeric arguments are assumed to have the minimum
+representable value for signed types with a precision greater than 1,
+and the maximum representable value otherwise. Unknown string arguments
+are assumed to be 1 character long. Enabling optimization will in most
+cases improve the accuracy of the warning, although in some cases it may
+also result in false positives.
+
+For example, at level @var{1}, the call to @code{sprintf} below is diagnosed
+because even with both @var{a} and @var{b} equal to zero, the terminating
+NUL character (@code{'\0'}) appended by the function to the destination
+buffer will be written past its end. Increasing the size of the buffer by
+a single byte is sufficient to avoid the warning. At level @var{2}, the call
+is again diagnosed, but this time because with @var{a} equal to a 32-bit
+@code{INT_MIN} the first @code{%i} directive will write some of its digits
+beyond the end of the destination buffer. To make the call safe regardless
+of the values of the two variables the size of the destination buffer must
+be increased to at least 34 bytes. GCC includes the minimum size of the
+buffer in an inforational note following the warning.
+
+@smallexample
+void f (int a, int b)
+@{
+ char buf [12];
+ sprintf (buf, "a = %i, b = %i\n", a, b);
+@}
+@end smallexample
+
+An alternative to increasing the size of the destination buffer is to
+constrain the range of formatted values. The maximum length of string
+arguments can be bounded by specifying the precision in the fortmat
+directive. When numeric arguments of format directives can be assumed
+to be bounded by less than the precision of their type, choosing
+an appropriate length modifier to the format character will reduce
+the minimum buffer size. For exampe, if @var{a} and @var{b} above can
+be assumed to be within the precision of the @code{short int} type then
+using either the @code{%hi} format directive or casting the argument to
+@code{short} reduces the maximum required size of the buffer to 24 bytes.
+
+@smallexample
+void f (int a, int b)
+@{
+ char buf [23];
+ sprintf (buf, "a = %hi, b = %i\n", a, (short)b);
+@}
+@end smallexample
+
+@item -Wformat-length
+@itemx -Wformat-length=@var{level}
+@opindex Wformat-length
+@opindex Wno-format-length
+@opindex ffreestanding
+@opindex fno-builtin
+@opindex Wformat-length=
+
+The @option{-Wformat-length} option causes GCC to attempt to detect calls
+to formatting functions such as @code{sprintf} that might overflow the
+destination buffer, or bounded functions like @code{snprintf} that result
+in output truncation. GCC counts the number of bytes that each format
+string and directive within it writes into the provided buffer and, when
+it detects that more bytes that fit in the destination buffer may be output,
+it emits a warning. Directives whose arguments have values that can be
+determined at compile-time account for the exact number of bytes they write.
+Directives with arguments whose values cannot be determined are processed
+based on heuristics that depend on the @var{level} argument to the option,
+and on optimization. The default setting of @var{level} is 1. Level
+@var{1} employs a conservative approach that warns only about calls that
+most likely overflow the buffer or result in output truncation. At this
+level, numeric arguments to format directives whose values are unknown
+are assumed to have the value of one, and strings of unknown length are
+assumed to have a length of zero. Numeric argument that are known to
+be bounded to a subrange of their type are assumed to take on the value
+within the range that results in the most bytes on output. Level @var{2}
+warns also bout calls that may overflow the destination buffer or result
+in truncation given an argument of sufficient length or magnitude. At
+this level, unknown numeric arguments are assumed to have the minimum
+representable value for signed types with a precision greater than 1,
+and the maximum representable value otherwise. Unknown string arguments
+are assumed to be 1 character long. Enabling optimization will in most
+cases improve the accuracy of the warning, although in some cases it may
+also result in false positives.
+
+For example, at level @var{1}, the call to @code{sprintf} below is diagnosed
+because even with both @var{a} and @var{b} equal to zero, the terminating
+NUL character (@code{'\0'}) appended by the function to the destination
+buffer will be written past its end. Increasing the size of the buffer by
+a single byte is sufficient to avoid the warning. At level @var{2}, the call
+is again diagnosed, but this time because with @var{a} equal to a 32-bit
+@code{INT_MIN} the first @code{%i} directive will write some of its digits
+beyond the end of the destination buffer. To make the call safe regardless
+of the values of the two variables the size of the destination buffer must
+be increased to at least 34 bytes. GCC includes the minimum size of the
+buffer in an inforational note following the warning.
+
+@smallexample
+void f (int a, int b)
+@{
+ char buf [12];
+ sprintf (buf, "a = %i, b = %i\n", a, b);
+@}
+@end smallexample
+
+An alternative to increasing the size of the destination buffer is to
+constrain the range of formatted values. The maximum length of string
+arguments can be bounded by specifying the precision in the fortmat
+directive. When numeric arguments of format directives can be assumed
+to be bounded by less than the precision of their type, choosing
+an appropriate length modifier to the format character will reduce
+the minimum buffer size. For exampe, if @var{a} and @var{b} above can
+be assumed to be within the precision of the @code{short int} type then
+using either the @code{%hi} format directive or casting the argument to
+@code{short} reduces the maximum required size of the buffer to 24 bytes.
+
+@smallexample
+void f (int a, int b)
+@{
+ char buf [23];
+ sprintf (buf, "a = %hi, b = %i\n", a, (short)b);
+@}
+@end smallexample
+
@item -Wno-format-zero-length
@opindex Wno-format-zero-length
@opindex Wformat-zero-length
@@ -7765,6 +7915,30 @@ dependent on the structure of loops within the source code.
Disabled at level @option{-Os}.
+@item -fprintf-return-value
+@opindex fprintf-return-value
+Substitute constants for known return value of formatted output functions
+such as @code{sprintf}, @code{snprintf}, @code{vsprintf}, and @code{vsnprintf}
+(but not @code{printf} of @code{fprintf}. This optimization makes it possible
+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
@@ -5331,6 +5331,20 @@ In either case, it remains possible to select code-generation for the alternate
scheme, by means of compiler command line switches.
@end defmac
+@deftypefn {Target Hook} TARGET_LIBC_PRINTF_ROUND_MODE ()
+A hook to determine the target @code{printf} implementation's rounding
+mode represented as an MPFR rounding specifier character. The default
+is @code{'N'} for round to nearest. Zero means to use no explicit
+rounding mode and rely on the MPFR default (also round to nearest).
+@end deftypefn
+
+@deftypefn {Target Hook} {const char *} TARGET_LIBC_PRINTF_POINTER_FORMAT (tree, const char **@var{flags})
+A hook to 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
@@ -4079,6 +4079,20 @@ In either case, it remains possible to select code-generation for the alternate
scheme, by means of compiler command line switches.
@end defmac
+@deftypefn {Target Hook} TARGET_LIBC_PRINTF_ROUND_MODE ()
+A hook to determine the target @code{printf} implementation's rounding
+mode represented as an MPFR rounding specifier character. The default
+is @code{'N'} for round to nearest. Zero means to use no explicit
+rounding mode and rely on the MPFR default (also round to nearest).
+@end deftypefn
+
+@deftypefn {Target Hook} {const char *} TARGET_LIBC_PRINTF_POINTER_FORMAT (tree, const char **@var{flags})
+A hook to 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
@@ -4051,7 +4051,7 @@ parser::parse_expr ()
else if (force_capture)
{
unsigned num = capture_ids->elements ();
- char id[8];
+ char id[13]; /* Big enough for a 32-bit UINT_MAX. */
bool existed;
sprintf (id, "__%u", num);
capture_ids->get_or_insert (xstrdup (id), &existed);
@@ -486,7 +486,7 @@ make_vector_modes (enum mode_class cl, unsigned int width,
{
struct mode_data *m;
struct mode_data *v;
- char buf[8];
+ char buf[12];
unsigned int ncomponents;
enum mode_class vclass = vector_class (cl);
@@ -1159,21 +1159,30 @@ gimple_fold_builtin_memset (gimple_stmt_iterator *gsi, tree c, tree len)
}
-/* Return the string length, maximum string length or maximum value of
- ARG in LENGTH.
- If ARG is an SSA name variable, follow its use-def chains. If LENGTH
- is not NULL and, for TYPE == 0, its value is not equal to the length
- we determine or if we are unable to determine the length or value,
- return false. VISITED is a bitmap of visited variables.
- TYPE is 0 if string length should be returned, 1 for maximum string
- length and 2 for maximum value ARG can have. */
+/* Obtain the minimum and maximum string length or minimum and maximum
+ value of ARG in LENGTH[0] and LENGTH[1], respectively.
+ If ARG is an SSA name variable, follow its use-def chains. When
+ TYPE == 0, if LENGTH[1] is not equal to the length we determine or
+ if we are unable to determine the length or value, return False.
+ VISITED is a bitmap of visited variables.
+ TYPE is 0 if string length should be obtained, 1 for maximum string
+ length and 2 for maximum value ARG can have.
+ When FUZZY is set and the length of a string cannot be determined,
+ the function instead considers as the maximum possible length the
+ size of a character array it may refer to. */
static bool
-get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
+get_range_strlen (tree arg, tree length[2], bitmap *visited, int type,
+ bool fuzzy)
{
tree var, val;
gimple *def_stmt;
+ /* The minimum and maximum length. The MAXLEN pointer stays unchanged
+ but MINLEN may be cleared during the execution of the function. */
+ tree *minlen = length;
+ tree* const maxlen = length + 1;
+
if (TREE_CODE (arg) != SSA_NAME)
{
/* We can end up with &(*iftmp_1)[0] here as well, so handle it. */
@@ -1184,8 +1193,8 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
tree aop0 = TREE_OPERAND (TREE_OPERAND (arg, 0), 0);
if (TREE_CODE (aop0) == INDIRECT_REF
&& TREE_CODE (TREE_OPERAND (aop0, 0)) == SSA_NAME)
- return get_maxval_strlen (TREE_OPERAND (aop0, 0),
- length, visited, type);
+ return get_range_strlen (TREE_OPERAND (aop0, 0),
+ length, visited, type, fuzzy);
}
if (type == 2)
@@ -1197,26 +1206,65 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
}
else
val = c_strlen (arg, 1);
+
+ if (!val)
+ {
+ if (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;
+ }
+ }
+ else
+ return false;
+ }
+
if (!val)
return false;
- if (*length)
+ if (minlen
+ && (!*minlen
+ || (type > 0
+ && TREE_CODE (*minlen) == INTEGER_CST
+ && TREE_CODE (val) == INTEGER_CST
+ && tree_int_cst_lt (val, *minlen))))
+ *minlen = val;
+
+ if (*maxlen)
{
if (type > 0)
{
- if (TREE_CODE (*length) != INTEGER_CST
+ if (TREE_CODE (*maxlen) != INTEGER_CST
|| TREE_CODE (val) != INTEGER_CST)
return false;
- if (tree_int_cst_lt (*length, val))
- *length = val;
+ if (tree_int_cst_lt (*maxlen, val))
+ *maxlen = val;
return true;
}
- else if (simple_cst_equal (val, *length) != 1)
+ else if (simple_cst_equal (val, *maxlen) != 1)
return false;
}
- *length = val;
+ *maxlen = val;
return true;
}
@@ -1244,14 +1292,14 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
|| gimple_assign_unary_nop_p (def_stmt))
{
tree rhs = gimple_assign_rhs1 (def_stmt);
- return get_maxval_strlen (rhs, length, visited, type);
+ return get_range_strlen (rhs, length, visited, type, fuzzy);
}
else if (gimple_assign_rhs_code (def_stmt) == COND_EXPR)
{
tree op2 = gimple_assign_rhs2 (def_stmt);
tree op3 = gimple_assign_rhs3 (def_stmt);
- return get_maxval_strlen (op2, length, visited, type)
- && get_maxval_strlen (op3, length, visited, type);
+ return get_range_strlen (op2, length, visited, type, fuzzy)
+ && get_range_strlen (op3, length, visited, type, fuzzy);
}
return false;
@@ -1274,7 +1322,8 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
if (arg == gimple_phi_result (def_stmt))
continue;
- if (!get_maxval_strlen (arg, length, visited, type))
+ if (!get_range_strlen (arg, length, visited, type, fuzzy)
+ && !fuzzy)
return false;
}
}
@@ -1285,17 +1334,39 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
}
}
+/* Determine the minimum and maximum value or string length that ARG
+ refers to and store each in the first two elements of MINMAXLEN.
+ For expressions that point to strings of unknown lengths that are
+ character arrays use the upper bound of the array as the maximum
+ length. For example, given an expression like 'x ? array : "xyz"'
+ and array being 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];
}
@@ -24,6 +24,8 @@ along with GCC; see the file COPYING3. If not see
extern tree canonicalize_constructor_val (tree, tree);
extern tree get_symbol_constant_value (tree);
+extern void get_range_strlen (tree, tree[2]);
+extern tree get_maxval_strlen (tree, int);
extern void gimplify_and_update_call_from_tree (gimple_stmt_iterator *, tree);
extern bool fold_stmt (gimple_stmt_iterator *);
extern bool fold_stmt (gimple_stmt_iterator *, tree (*) (tree));
new file mode 100644
@@ -0,0 +1,2489 @@
+/* Copyright (C) 2016 Free Software Foundation, Inc.
+ Contributed by Martin Sebor <msebor@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-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"
+
+ extern void debug_tree (tree);
+
+
+#ifndef TARGET_LIBC_PRINTF_ROUND_MODE
+# define TARGET_LIBC_PRINTF_ROUND_MODE default_libc_printf_round_mode
+#endif
+
+#ifndef TARGET_LIBC_PRINTF_POINTER_FORMAT
+# define TARGET_LIBC_PRINTF_POINTER_FORMAT default_libc_printf_pointer_format
+#endif
+
+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 formatting function. */
+
+struct format_result
+{
+ /* Number of characters written by the formatting function, exact,
+ minimum and maximum when an exact number cannot be determined.
+ Setting the minimum to 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 formatting 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 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 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;
+}
+
+/* Copied from c-family/c-format.c. Given a string S of length LINE_WIDTH,
+ find the visual column corresponding to OFFSET bytes. */
+
+static unsigned int
+location_column_from_byte_offset (const char *s, int line_width,
+ unsigned int offset)
+{
+ const char * c = s;
+ if (*c != '"')
+ return 0;
+
+ c++, offset--;
+ while (offset > 0)
+ {
+ if (c - s >= line_width)
+ return 0;
+
+ switch (*c)
+ {
+ case '\\':
+ c++;
+ if (c - s >= line_width)
+ return 0;
+ switch (*c)
+ {
+ case '\\': case '\'': case '"': case '?':
+ case '(': case '{': case '[': case '%':
+ case 'a': case 'b': case 'f': case 'n':
+ case 'r': case 't': case 'v':
+ case 'e': case 'E':
+ c++, offset--;
+ break;
+
+ default:
+ return 0;
+ }
+ break;
+
+ case '"':
+ /* We found the end of the string too early. */
+ return 0;
+
+ default:
+ c++, offset--;
+ break;
+ }
+ }
+ return c - s;
+}
+
+/* Copied from c-family/c-format.c. Return a location that encodes
+ the same location as LOC but shifted by OFFSET bytes. */
+
+static location_t
+location_from_offset (location_t loc, int offset)
+{
+ gcc_checking_assert (offset >= 0);
+ if (linemap_location_from_macro_expansion_p (line_table, loc)
+ || offset < 0)
+ return loc;
+
+ expanded_location s = expand_location_to_spelling_point (loc);
+ int line_width;
+ const char *line = location_get_source_line (s.file, s.line, &line_width);
+ if (line == NULL)
+ return loc;
+ line += s.column - 1 ;
+ line_width -= s.column - 1;
+ unsigned int column =
+ location_column_from_byte_offset (line, line_width, (unsigned) offset);
+
+ return linemap_position_for_loc_and_offset (line_table, loc, column);
+}
+
+/* Return a location that encodes the same location as LOC but shifted
+ by OFFSET bytes and ending LENGTH bytes including the caret. For
+ example, to create a location for the %i directive embedded in the
+ format string below from its location, call the function with OFFSET
+ = 5 and LENGTH 3 to end up with the following:
+ sprintf ("abc-%3i-def", ...)
+ ^~~
+*/
+
+static location_t
+location_from_offset (location_t loc, int offset, int length)
+{
+ location_t beg = location_from_offset (loc, offset);
+ if (length == 0)
+ return beg;
+
+ location_t end = location_from_offset (loc, offset + length - 1);
+ return make_location (beg, beg, end);
+}
+
+/* Format length modifiers. */
+
+enum format_lengths
+{
+ FMT_LEN_none,
+ FMT_LEN_hh, // char argument
+ FMT_LEN_h, // short
+ FMT_LEN_l, // long
+ FMT_LEN_ll, // long long
+ FMT_LEN_L, // long double (and GNU long long)
+ FMT_LEN_z, // size_t
+ FMT_LEN_t, // ptrdiff_t
+ FMT_LEN_j // intmax_t
+};
+
+
+/* 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 (res.number_chars < navail)
+ {
+ range.min = navail - res.number_chars;
+ 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;
+}
+
+static inline unsigned HOST_WIDE_INT
+min_bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
+{
+ if (1 < warn_format_length || res.bounded)
+ {
+ /* At level 2, or when all directives output an exact number
+ of bytes or when their arguments were bounded by known
+ ranges, use the greater of the two byte counters if it's
+ valid to compute the result. */
+ if (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 formatting 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 = TARGET_LIBC_PRINTF_POINTER_FORMAT (arg, &flags);
+ if (!pfmt)
+ {
+ /* The format couldn't be determined. */
+ res.range.min = res.range.max = -1;
+ 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 = -1;
+ return res;
+ }
+
+ fmtresult res = fmtresult ();
+
+ /* Using either the range the non-constant argument is in, or its
+ type (either "formal" or actual), create a range of values that
+ constrain the length of output given the warning level. */
+ tree argmin = NULL_TREE;
+ tree argmax = NULL_TREE;
+
+ if (arg && TREE_CODE (arg) == SSA_NAME
+ && TREE_CODE (argtype) == INTEGER_TYPE)
+ {
+ /* Try to determine the range of values of the integer argument
+ (range information is not available for pointers). */
+ wide_int min, max;
+ enum value_range_type range_type = get_range_info (arg, &min, &max);
+ if (range_type == VR_RANGE)
+ {
+ res.argmin = build_int_cst (argtype, wi::fits_uhwi_p (min)
+ ? min.to_uhwi () : min.to_shwi ());
+ res.argmax = build_int_cst (argtype, wi::fits_uhwi_p (max)
+ ? max.to_uhwi () : max.to_shwi ());
+
+ /* For a range with a negative lower bound and a non-negative
+ upper bound, use one to determine the minimum number of bytes
+ on output and whichever of the two bounds that results in
+ the larger number of bytes on output for the upper bound.
+ For example, for arg in the range of [-3, 123], use 123 as
+ the upper bound for %i but -3 for %u. */
+ if (wi::neg_p (min) && !wi::neg_p (max))
+ {
+ argmin = build_int_cst (argtype, wi::fits_uhwi_p (min)
+ ? min.to_uhwi () : min.to_shwi ());
+
+ argmax = build_int_cst (argtype, wi::fits_uhwi_p (max)
+ ? max.to_uhwi () : max.to_shwi ());
+
+ int minbytes = format_integer (spec, res.argmin).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 the Value Range Propagation. */
+ res.bounded = true;
+ }
+ else if (range_type == VR_ANTI_RANGE)
+ {
+ /* Handle anti-ranges if/when bug 71690 is ever resolved. */
+ }
+ else if (range_type == VR_VARYING)
+ {
+ /* The argument here may be the result of promoting
+ the actual argument to int. Try to determine the
+ type of the actual argument before promotion and
+ narrow down its range that way. */
+ gimple *def = SSA_NAME_DEF_STMT (arg);
+ if (gimple_code (def) == GIMPLE_ASSIGN)
+ {
+ tree_code code = gimple_assign_rhs_code (def);
+ if (code == NOP_EXPR)
+ argtype = TREE_TYPE (gimple_assign_rhs1 (def));
+ }
+ }
+ }
+
+ if (!argmin)
+ {
+ /* For an unknown argument (e.g., one passed to a vararg
+ function) or one whose value range cannot be determined,
+ create a T_MIN constant if the argument's type is signed
+ and T_MAX otherwise, and use those to compute the range
+ of bytes that the directive can output. */
+ argmin = build_int_cst (argtype, 1);
+
+ int typeprec = TYPE_PRECISION (dirtype);
+ int argprec = TYPE_PRECISION (argtype);
+
+ if (argprec < typeprec || POINTER_TYPE_P (argtype))
+ {
+ if (TYPE_UNSIGNED (argtype))
+ argmax = build_all_ones_cst (argtype);
+ else
+ argmax = fold_build2 (LSHIFT_EXPR, argtype, integer_one_node,
+ build_int_cst (integer_type_node,
+ argprec - 1));
+ }
+ else
+ {
+ argmax = fold_build2 (LSHIFT_EXPR, dirtype, integer_one_node,
+ build_int_cst (integer_type_node,
+ typeprec - 1));
+ }
+ res.argmin = argmin;
+ res.argmax = argmax;
+ }
+
+ /* Recursively compute the minimum and maximum from the known range,
+ taking care to swap them if the lower bound results in longer
+ output than the upper bound (e.g., in the range [-1, 0]. */
+ res.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 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 = -1;
+ 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 = -1;
+ 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)
+ {
+ 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';
+
+ /* Round to nearest is the MPFR documented default. */
+ if (int rndspec = TARGET_LIBC_PRINTF_ROUND_MODE ())
+ *pfmt++ = rndspec;
+
+ /* Append the C type specifier and nul-terminate. */
+ *pfmt++ = spec.specifier;
+ *pfmt = '\0';
+
+ const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg);
+
+ /* Get the real type format desription for the target. */
+ 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 x;
+ mpfr_init2 (x, rfmt->p);
+ mpfr_from_real (x, rvp, MPFR_RNDN);
+
+ /* Format it. */
+ res.range.min = mpfr_snprintf (NULL, 0, fmtstr, x);
+ res.range.max = res.range.min;
+ 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;
+ }
+
+ 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_MAX);
+
+ res.bounded = res.range.max != HOST_WIDE_INT_MAX;
+ 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 the same . */
+ if (res.range.min == res.range.max && res.range.min < HOST_WIDE_INT_MAX)
+ res.bounded = true;
+
+ return res;
+}
+
+static void
+compute_format_length (const pass_sprintf_length::call_info &info,
+ format_result *res,
+ const char *cvtbeg,
+ size_t cvtlen,
+ size_t offset,
+ const conversion_spec &spec,
+ tree arg)
+{
+ /* Create a location for the whole directive from the % to the format
+ specifier. */
+ location_t dirloc = location_from_offset (info.fmtloc, offset + 1, cvtlen);
+
+ /* Bail when there is no function to compute the output length,
+ or when 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 = -1;
+ res->number_chars = -1;
+ }
+
+ 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 = -1;
+ return;
+ }
+
+ /* Compute the number of available bytes in the destination. There
+ must always be at least one byte of space for the terminating
+ NUL that's appended after the format string has been processed. */
+ unsigned HOST_WIDE_INT navail = 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)
+ {
+ 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 = warning_at (dirloc, 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 = warning_at (dirloc, 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))
+ {
+ 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 = warning_at (dirloc, 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 = warning_at (dirloc, 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 (dirloc, "directive argument %qE", fmtres.argmin);
+ else if (fmtres.bounded)
+ inform (dirloc, "directive argument in the range [%qE, %qE]",
+ fmtres.argmin, fmtres.argmax);
+ else
+ inform (dirloc,
+ "using the range [%qE, %qE] for directive argument",
+ fmtres.argmin, fmtres.argmax);
+ }
+ }
+
+ /* Disable exact length checking but adjust the minimum and maximum. */
+ res->number_chars = -1;
+ if (res->number_chars_max < 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 = warning_at (dirloc, OPT_Wformat_length_, fmtstr,
+ (int)cvtlen, cvtbeg, fmtres.range.min,
+ navail);
+ }
+ *res += fmtres.range.min;
+ }
+}
+
+static void
+add_bytes (const pass_sprintf_length::call_info &info,
+ const char *beg,
+ const char *end,
+ format_result *res)
+{
+ if (res->number_chars_min >= 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);
+
+ /* Create a location range from the last processed format character
+ to the current one (or the end of the format string). */
+ location_t loc = location_from_offset (info.fmtloc, off + 1, len);
+
+ /* Is the output of the last directive the result of the argument
+ being within a range whose lower bound would fit in the buffer
+ but the upper bound would not? If so, use the word "may" to
+ indicate that the overflow/truncation may (but need not) happen. */
+ bool boundrange
+ = (res->number_chars_min < res->number_chars_max
+ && res->number_chars_min < info.objsize);
+
+ res->warned = true;
+
+ 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")));
+
+ warning_at (loc, OPT_Wformat_length_, text);
+ }
+ else
+ {
+ /* There isn't enough room for 1 or more characters that remain
+ to copy from the format string. */
+ const char *text
+ = (info.bounded // Snprintf and the like.
+ ? (boundrange
+ ? G_("output may be truncated at or before format character "
+ "%qc at offset %wu")
+ : G_("output truncated at format character %qc at offset %wu"))
+ : (res->number_chars >= 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")));
+
+ warning_at (loc, OPT_Wformat_length_, text, info.fmtstr[off], off);
+ }
+ }
+
+ if (res->warned)
+ {
+ /* Help the user figure out how big a buffer they need. */
+
+ location_t callloc = gimple_location (info.callstmt);
+
+ unsigned HOST_WIDE_INT min = 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
+ ? "format output %wu byte into a destination of size %wu"
+ : "format output %wu bytes into a destination of size %wu"),
+ nbytes + exact, info.objsize);
+ }
+
+ *res += nbytes;
+}
+
+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
+ until determined otherwise. */
+ res->bounded = true;
+ res->constant = true;
+ 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 (*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)
+ {
+ 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;
+
+ /* Offset of the beginning of the directive from the beginning
+ of the format string. */
+ size_t diroff = dir - info.fmtstr;
+
+ /* Extract the argument if the directive takes one 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++);
+
+ ::compute_format_length (info, res, dir, dirlen, diroff, spec, arg);
+ }
+}
+
+/* Return the size of the object referenced by the expression DEST if
+ available, or -1 otherwise.
+ Try to work around some of the limitations of __builtin_object_size
+ in the common simple case when DEST is a POINTER_PLUS_EXPR involving
+ an array. */
+
+static unsigned HOST_WIDE_INT
+get_destination_size (tree dest)
+{
+ /* Try to use __builtin_object_size although it rarely returns
+ a useful result even for straighforward cases. */
+ int ost = warn_format_length < 2 ? 0 : 2;
+ /* A valid __builtin_object_size result for OST of zero is less
+ than SIZE_MAX, and for OST of 2 is greater than zero. */
+ unsigned HOST_WIDE_INT size = compute_builtin_object_size (dest, ost);
+ if ((!ost && size < (unsigned HOST_WIDE_INT)-1) || (ost && size))
+ return size;
+
+ /* If __builtin_object_size fails to deliver, try to compute
+ it for the very basic (but common) cases. */
+ if (TREE_CODE (dest) == SSA_NAME
+ && POINTER_TYPE_P (TREE_TYPE (dest)))
+ {
+ gimple *def = SSA_NAME_DEF_STMT (dest);
+ if (gimple_code (def) == GIMPLE_ASSIGN)
+ {
+ tree_code code = gimple_assign_rhs_code (def);
+ if (code == POINTER_PLUS_EXPR)
+ {
+ tree off = gimple_assign_rhs2 (def);
+ dest = gimple_assign_rhs1 (def);
+
+ if (cst_and_fits_in_hwi (off))
+ {
+ unsigned HOST_WIDE_INT size = get_destination_size (dest);
+ if (size < HOST_WIDE_INT_MAX)
+ return size - tree_to_shwi (off);
+ }
+ }
+ }
+ }
+
+ return HOST_WIDE_INT_MAX;
+}
+
+/* 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. */
+
+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);
+ if (lhs && res.bounded && res.number_chars < HOST_WIDE_INT_MAX)
+ {
+ /* Replace the left-hand side of the call with the constant
+ result of the formatting 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 if (dump_file)
+ {
+ 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, " return value in range [%lu, %lu]%s.\n",
+ (unsigned long)res.number_chars_min,
+ (unsigned long)res.number_chars_max, ign);
+ else
+ fprintf (dump_file, " return value %lu%s.\n",
+ (unsigned long)res.number_chars, ign);
+ }
+}
+
+/* Determine if a GIMPLE CALL is one to one of the sprintf-like built-in
+ functions and if so, handle it. */
+
+void
+pass_sprintf_length::handle_gimple_call (gimple_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)
+ 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 less than that. */
+ if (dstsize >= HOST_WIDE_INT_MAX)
+ warning_at (gimple_location (info.callstmt), 0,
+ "specified destination size %wu too large",
+ dstsize);
+ }
+ else if (TREE_CODE (size) == SSA_NAME)
+ {
+ /* Try to determine the range of values of the argument
+ and use the greater of the two at -Wformat-level 1 and
+ the smaller of them at level 2. */
+ wide_int min, max;
+ enum value_range_type range_type
+ = get_range_info (size, &min, &max);
+ if (range_type == VR_RANGE)
+ {
+ dstsize
+ = (warn_format_length < 2
+ ? wi::fits_uhwi_p (max) ? max.to_uhwi () : max.to_shwi ()
+ : wi::fits_uhwi_p (min) ? min.to_uhwi () : min.to_shwi ());
+ }
+ }
+ }
+
+ if (idx_objsize != 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), 0,
+ "specified size %wu exceeds the size %wu "
+ "of the destination object", dstsize, objsize);
+ }
+
+ /* Cap the object size at the largest valid size to disable
+ size tracking. */
+ if (info.objsize > HOST_WIDE_INT_MAX)
+ info.objsize = HOST_WIDE_INT_MAX;
+ }
+
+ 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 formatting
+ function, including the terminating NUL. */
+ format_result res;
+ 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);
+}
+
+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. */
+
+gimple_opt_pass *
+make_pass_sprintf_length (gcc::context *ctxt)
+{
+ return new pass_sprintf_length (ctxt);
+}
@@ -5346,7 +5346,8 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
flexibility, split it into separate input and output
operands. */
tree input;
- char buf[10];
+ /* Buffer big enough to format a 32-bit UINT_MAX into. */
+ char buf[11];
/* Turn the in/out constraint into an output constraint. */
char *p = xstrdup (constraint);
@@ -5356,7 +5357,7 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
/* And add a matching input constraint. */
if (allows_reg)
{
- sprintf (buf, "%d", i);
+ sprintf (buf, "%u", i);
/* If there are multiple alternatives in the constraint,
handle each of them individually. Those that allow register
@@ -9050,7 +9050,8 @@ Call_expression::do_flatten(Gogo* gogo, Named_object*,
Location loc = this->location();
int i = 0;
- char buf[10];
+ // Big enough for a 32-bit int plus the string. */
+ char buf[14];
for (Typed_identifier_list::const_iterator p = results->begin();
p != results->end();
++p, ++i)
@@ -771,7 +771,9 @@ pass_manager::register_one_dump_file (opt_pass *pass)
{
char *dot_name, *flag_name, *glob_name;
const char *name, *full_name, *prefix;
- char num[10];
+
+ /* Buffer big enough to format a 32-bit UINT_MAX into. */
+ char num[11];
int flags, id;
int optgroup_flags = OPTGROUP_NONE;
gcc::dump_manager *dumps = m_ctxt->get_dumps ();
@@ -779,7 +781,7 @@ pass_manager::register_one_dump_file (opt_pass *pass)
/* See below in next_pass_1. */
num[0] = '\0';
if (pass->static_pass_number != -1)
- sprintf (num, "%d", ((int) pass->static_pass_number < 0
+ sprintf (num, "%u", ((int) pass->static_pass_number < 0
? 1 : pass->static_pass_number));
/* The name is both used to identify the pass for the purposes of plugins,
@@ -43,6 +43,7 @@ along with GCC; see the file COPYING3. If not see
NEXT_PASS (pass_warn_function_return);
NEXT_PASS (pass_expand_omp);
NEXT_PASS (pass_build_cgraph_edges);
+ NEXT_PASS (pass_sprintf_length, false);
TERMINATE_PASS_LIST (all_lowering_passes)
/* Interprocedural optimization passes. */
@@ -303,6 +304,7 @@ along with GCC; see the file COPYING3. If not see
NEXT_PASS (pass_simduid_cleanup);
NEXT_PASS (pass_lower_vector_ssa);
NEXT_PASS (pass_cse_reciprocals);
+ NEXT_PASS (pass_sprintf_length, true);
NEXT_PASS (pass_reassoc, false /* insert_powi_p */);
NEXT_PASS (pass_strength_reduction);
NEXT_PASS (pass_split_paths);
@@ -694,8 +694,10 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
i = 0;
FOR_EACH_CALL_EXPR_ARG (arg, iter, node)
{
- char temp[10];
- sprintf (temp, "arg %d", i);
+ /* Buffer big enough to format a 32-bit UINT_MAX into, plus
+ the text. */
+ char temp[15];
+ sprintf (temp, "arg %u", i);
print_node (file, temp, arg, indent + 4);
i++;
}
@@ -706,7 +708,9 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
for (i = 0; i < len; i++)
{
- char temp[10];
+ /* Buffer big enough to format a 32-bit UINT_MAX into, plus
+ the text. */
+ char temp[15];
sprintf (temp, "arg %d", i);
print_node (file, temp, TREE_OPERAND (node, i), indent + 4);
@@ -814,7 +818,9 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
for (i = 0; i < len; i++)
if (TREE_VEC_ELT (node, i))
{
- char temp[10];
+ /* Buffer big enough to format a 32-bit UINT_MAX into, plus
+ the text. */
+ char temp[15];
sprintf (temp, "elt %d", i);
print_node (file, temp, TREE_VEC_ELT (node, i), indent + 4);
}
@@ -1421,6 +1421,55 @@ no_c99_libc_has_function (enum function_class fn_class ATTRIBUTE_UNUSED)
return false;
}
+/* Return the MPFR rounding specifier character corresponding to
+ the default printf rounding behavior or -1 for don't care. */
+
+int
+default_libc_printf_round_mode (void)
+{
+ /* N for round to nearest. This is the IEEE 754 default rounding
+ mode used by, for example, Glibc and Solaris libc. */
+ return 'N';
+}
+
+/* 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_libc_printf_pointer_format (tree, const char **flags)
+{
+ *flags = "";
+
+ return "%zx";
+}
+
+/* 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";
+}
+
+/* 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_libc_printf_pointer_format (tree, const char **flags)
+{
+ *flags = "#";
+
+ return "%zx";
+}
+
tree
default_builtin_tm_load_store (tree ARG_UNUSED (type))
{
@@ -190,6 +190,11 @@ extern bool default_libc_has_function (enum function_class);
extern bool no_c99_libc_has_function (enum function_class);
extern bool gnu_libc_has_function (enum function_class);
+extern int default_libc_printf_round_mode (void);
+extern const char* default_libc_printf_pointer_format (tree, const char **);
+extern const char* gnu_libc_printf_pointer_format (tree, const char **);
+extern const char* solaris_libc_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);
new file mode 100644
new file mode 100644
new file mode 100644
@@ -0,0 +1,1308 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+/* When debugging, define LINE to the line number of the test case to exercise
+ and avoid exercising any of the others. The buffer and objsize macros
+ below make use of LINE to avoid warnings for other lines. */
+#ifndef LINE
+# define LINE 0
+#endif
+
+char buffer [256];
+extern char *ptr;
+
+#define buffer(size) \
+ (!LINE || __LINE__ == LINE ? buffer + sizeof buffer - size : ptr)
+
+#define objsize(size) (!LINE || __LINE__ == LINE ? size : __SIZE_MAX__ / 2)
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+const char s0[] = "";
+const char s1[] = "1";
+const char s2[] = "12";
+const char s3[] = "123";
+const char s4[] = "1234";
+const char s5[] = "12345";
+const char s6[] = "123456";
+const char s7[] = "1234567";
+const char s8[] = "12345678";
+
+void sink (void*);
+
+/* Macro to verify that calls to __builtin_sprintf (i.e., with no size
+ argument) issue diagnostics by correctly determining the size of
+ the destination buffer. */
+#define T(size, fmt, ...) \
+ __builtin_sprintf (buffer (size), fmt, __VA_ARGS__), sink (buffer);
+
+/* Exercise the "%c" and "%lc" directive with constant arguments. */
+
+void test_sprintf_c_const (void)
+{
+ T (0, "%c", 0); /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+ T (1, "%c", 0); /* { dg-warning "writing a terminating nul past the end" } */
+ T (1, "%c", '1'); /* { dg-warning "nul past the end" } */
+ T (2, "%c", '1');
+ T (2, "%2c", '1'); /* { dg-warning "nul past the end" } */
+ T (2, "%3c", '1'); /* { dg-warning "into a region" } */
+ T (2, "%c%c", '1', '2'); /* { dg-warning "nul past the end" } */
+ T (3, "%c%c", '1', '2');
+
+ T (2, "%1$c%2$c", '1', '2'); /* { dg-warning "does not support %n.|nul past the end" } */
+ T (3, "%1$c%2$c", '1', '2');
+}
+
+/* Exercise the "%p" directive with constant arguments. */
+
+void test_sprintf_p_const (void)
+{
+ /* 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 } 69 } */
+
+ /* 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 } 81 } */
+ /* { dg-warning "nul past the end" "Generic %p" { target *-*-uclinux } 81 } */
+}
+
+/* Verify that no warning is issued for calls that write into a flexible
+ array member whose size isn't known. Also verify that calls that use
+ a flexible array member as an argument to the "%s" directive do not
+ cause a warning. */
+
+void test_sprintf_flexarray (void *p, int i)
+{
+ struct S
+ {
+ int n;
+ char a [];
+ } *s = p;
+
+ __builtin_sprintf (s->a, "%c", 'x');
+
+ __builtin_sprintf (s->a, "%s", "");
+ __builtin_sprintf (s->a, "%s", "abc");
+ __builtin_sprintf (s->a, "abc%sghi", "def");
+
+ __builtin_sprintf (s->a, "%i", 1234);
+
+ __builtin_sprintf (buffer (1), "%s", s->a);
+ __builtin_sprintf (buffer (1), "%s", s [i].a);
+}
+
+/* Verify that the note printed along with the diagnostic mentions
+ the correct sizes and refers to the location corresponding to
+ the affected directive. */
+
+void test_sprintf_note (void)
+{
+#define P __builtin_sprintf
+
+ /* Diagnostic column numbers are 1-based. */
+
+ P (buffer (0), /* { dg-message "format output 4 bytes into a destination of size 0" } */
+ "%c%s%i", '1', "2", 3); /* { dg-warning "7:.%c. directive writing 1 byte into a region of size 0" } */
+
+ P (buffer (1), /* { dg-message "format output 6 bytes into a destination of size 1" } */
+ "%c%s%i", '1', "23", 45); /* { dg-warning "9:.%s. directive writing 2 bytes into a region of size 0" } */
+
+ P (buffer (2), /* { dg-message "format output 6 bytes into a destination of size 2" } */
+ "%c%s%i", '1', "2", 345); /* { dg-warning "11:.%i. directive writing 3 bytes into a region of size 0" } */
+
+ P (buffer (6), /* { dg-message "format output 7 bytes into a destination of size 6" } */
+ "%c%s%i", '1', "2", 3456); /* { dg-warning "13:writing a terminating nul past the end of the destination" } */
+}
+
+#undef T
+#define T(size, fmt, ...) \
+ __builtin___sprintf_chk (buffer (size), 0, objsize (size), fmt, __VA_ARGS__)
+
+/* Exercise the "%c" and "%lc" directive with constant arguments. */
+
+void test_sprintf_chk_c_const (void)
+{
+ /* Verify the full text of the diagnostic for just the distinct messages
+ and use abbreviations in subsequent test cases. */
+ T (0, "%c", 0); /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+ T (1, "%c", 0); /* { dg-warning "writing a terminating nul past the end" } */
+ T (1, "%c", '1'); /* { dg-warning "nul past the end" } */
+ T (2, "%c", '1');
+ T (2, "%2c", '1'); /* { dg-warning "nul past the end" } */
+ T (2, "%3c", '1'); /* { dg-warning "into a region" } */
+ T (2, "%c%c", '1', '2'); /* { dg-warning "nul past the end" } */
+ T (3, "%c%c", '1', '2');
+
+ /* Wide characters. */
+ T (0, "%lc", 0); /* { dg-warning "nul past the end" } */
+ T (1, "%lc", 0);
+ T (1, "%lc%lc", 0, 0);
+ T (2, "%lc", 0);
+ T (2, "%lc%lc", 0, 0);
+
+ /* The following could result in as few as no bytes and in as many as
+ MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+ the write cannot be reliably diagnosed. */
+ T (2, "%lc", L'1');
+ T (2, "%1lc", L'1');
+ /* Writing some unknown number of bytes into a field two characters wide. */
+ T (2, "%2lc", L'1'); /* { dg-warning "nul past the end" } */
+
+ T (3, "%lc%c", L'1', '2');
+ /* Here in the best case each argument will format as single character,
+ causing the terminating NUL to be written past the end. */
+ T (3, "%lc%c%c", L'1', '2', '3'); /* { dg-warning "nul past the end" } */
+ T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%s" and "%ls" directive with constant arguments. */
+
+void test_sprintf_chk_s_const (void)
+{
+ T (0, "%*s", 0, ""); /* { dg-warning "nul past the end" } */
+ T (0, "%*s", 0, s0); /* { dg-warning "nul past the end" } */
+ T (1, "%*s", 0, "");
+ T (1, "%*s", 0, s0);
+ T (1, "%*s", 0, "\0");
+ T (1, "%*s", 0, "1"); /* { dg-warning "nul past the end" } */
+ T (1, "%*s", 0, s1); /* { dg-warning "nul past the end" } */
+ T (1, "%1s", ""); /* { dg-warning "nul past the end" } */
+ T (1, "%1s", s0); /* { dg-warning "nul past the end" } */
+ T (1, "%*s", 1, ""); /* { dg-warning "nul past the end" } */
+ T (1, "%*s", 1, s0); /* { dg-warning "nul past the end" } */
+
+ T (1, "%.0s", "123");
+ T (1, "%.0s", s3);
+ T (1, "%.*s", 0, "123");
+ T (1, "%.*s", 0, s3);
+ T (1, "%.1s", "123"); /* { dg-warning "nul past the end" } */
+ T (1, "%.1s", s3); /* { dg-warning "nul past the end" } */
+ T (1, "%.*s", 1, "123"); /* { dg-warning "nul past the end" } */
+ T (1, "%.*s", 1, s3); /* { dg-warning "nul past the end" } */
+
+ T (2, "%.*s", 0, "");
+ T (2, "%.*s", 0, "1");
+ T (2, "%.*s", 0, s1);
+ T (2, "%.*s", 0, "1\0");
+ T (2, "%.*s", 0, "12");
+ T (2, "%.*s", 0, s2);
+
+ T (2, "%.*s", 1, "");
+ T (2, "%.*s", 1, "1");
+ T (2, "%.*s", 1, s1);
+ T (2, "%.*s", 1, "1\0");
+ T (2, "%.*s", 1, "12");
+ T (2, "%.*s", 1, s2);
+
+ T (2, "%.*s", 2, "");
+ T (2, "%.*s", 2, "1");
+ T (2, "%.*s", 2, s1);
+ T (2, "%.*s", 2, "1\0");
+ T (2, "%.*s", 2, "12"); /* { dg-warning "nul past the end" } */
+ T (2, "%.*s", 2, s2); /* { dg-warning "nul past the end" } */
+
+ T (2, "%.*s", 3, "");
+ T (2, "%.*s", 3, "1");
+ T (2, "%.*s", 3, s1);
+ T (2, "%.*s", 3, "1\0");
+ T (2, "%.*s", 3, "12"); /* { dg-warning "nul past the end" } */
+ T (2, "%.*s", 3, "123"); /* { dg-warning "into a region" } */
+ T (2, "%.*s", 3, s2); /* { dg-warning "nul past the end" } */
+ T (2, "%.*s", 3, s3); /* { dg-warning "into a region" } */
+
+ T (2, "%*s", 0, "");
+ T (2, "%*s", 0, "1");
+ T (2, "%*s", 0, s1);
+ T (2, "%*s", 0, "1\0");
+ T (2, "%*s", 0, "12"); /* { dg-warning "nul past the end" } */
+ T (2, "%*s", 0, s2); /* { dg-warning "nul past the end" } */
+
+ /* Multiple directives. */
+
+ T (1, "%s%s", "", "");
+ T (1, "%s%s", s0, s0);
+ T (1, "%s%s", "", "1"); /* { dg-warning "nul past the end" } */
+ T (1, "%s%s", s0, s1); /* { dg-warning "nul past the end" } */
+ T (1, "%s%s", "1", ""); /* { dg-warning "nul past the end" } */
+ T (1, "%s%s", s1, s0); /* { dg-warning "nul past the end" } */
+ T (1, "%s%s", "1", "2"); /* { dg-warning "into a region" } */
+ T (1, "%s%s", s1, s1); /* { dg-warning "into a region" } */
+
+ T (2, "%s%s", "", "");
+ T (2, "%s%s", "", "1");
+ T (2, "%s%s", "1", "");
+ T (2, "%s%s", "", "12"); /* { dg-warning "nul past the end" } */
+ T (2, "%s%s", "1", "2"); /* { dg-warning "nul past the end" } */
+ T (2, "%s%s", "12", "2"); /* { dg-warning "into a region" } */
+ T (2, "%s%s", "1", "23"); /* { dg-warning "into a region" } */
+ T (2, "%s%s", "12", "3"); /* { dg-warning "into a region" } */
+ T (2, "%s%s", "12", "34"); /* { dg-warning "into a region" } */
+
+ T (2, "_%s", "");
+ T (2, "%%%s", "");
+ T (2, "%s%%", "");
+ T (2, "_%s", "1"); /* { dg-warning "nul past the end" } */
+ T (2, "%%%s", "1"); /* { dg-warning "nul past the end" } */
+ T (2, "%s%%", "1"); /* { dg-warning "nul past the end" } */
+ T (2, "_%s", "12"); /* { dg-warning "into a region" } */
+ T (2, "__%s", "1"); /* { dg-warning "into a region" } */
+
+ T (2, "%1$s%2$s", "12", "3"); /* { dg-warning ".%2.s. directive writing 1 byte into a region of size 0" } */
+ T (2, "%1$s%1$s", "12"); /* { dg-warning "does not support|.%1.s. directive writing 2 bytes into a region of size 0" } */
+ T (2, "%2$s%1$s", "1", "23"); /* { dg-warning ".%1.s. directive writing 1 byte into a region of size 0" } */
+ T (2, "%2$s%2$s", "1", "23"); /* { dg-warning "unused|%2.s. directive writing 2 bytes into a region of size 0" } */
+
+ T (3, "__%s", "");
+ T (3, "__%s", "1"); /* { dg-warning "nul past the end" } */
+ T (3, "%s_%s", "", "");
+ T (3, "%s_%s", "1", "");
+ T (3, "%s_%s", "", "1");
+ T (3, "%s_%s", "1", "2"); /* { dg-warning "nul past the end" } */
+
+ /* Wide strings. */
+ T (0, "%ls", L""); /* { dg-warning "nul past the end" } */
+ T (1, "%ls", L"");
+ T (1, "%ls", L"\0");
+ T (1, "%1ls", L""); /* { dg-warning "nul past the end" } */
+
+ T (0, "%*ls", 0, L""); /* { dg-warning "nul past the end" } */
+ T (1, "%*ls", 0, L"");
+ T (1, "%*ls", 0, L"\0");
+ T (1, "%*ls", 1, L""); /* { dg-warning "nul past the end" } */
+
+ T (1, "%ls", L"1"); /* { dg-warning "nul past the end" } */
+ T (1, "%.0ls", L"1");
+ T (2, "%.0ls", L"1");
+ T (2, "%.1ls", L"1");
+ T (2, "%.*ls", 1, L"1");
+
+ /* The "%.2ls" directive below will write at a minimum 1 byte (because
+ L"1" is known and can be assumed to convert to at least one multibyte
+ character), and at most 2 bytes because of the precision. Since its
+ output is explicitly bounded it is diagnosed. */
+ T (2, "%.2ls", L"1"); /* { dg-warning "nul past the end" } */
+ T (2, "%.*ls", 2, L"1"); /* { dg-warning "nul past the end" } */
+
+ T (3, "%.0ls", L"1");
+ T (3, "%.1ls", L"1");
+ T (3, "%.2ls", L"1");
+}
+
+/* Exercise the "%hhd", "%hhi", "%hho", "%hhu", and "%hhx" directives
+ with constant arguments. */
+
+void test_sprintf_chk_hh_const (void)
+{
+ T (1, "%hhd", 0); /* { dg-warning "nul past the end" } */
+ T (1, "%hhd", 1); /* { dg-warning "nul past the end" } */
+ T (1, "%hhd", -1); /* { dg-warning "into a region" } */
+ T (1, "%+hhd", 0); /* { dg-warning "into a region" } */
+ T (1, "%+hhd", 1); /* { dg-warning "into a region" } */
+ T (1, "%-hhd", 0); /* { dg-warning "nul past the end" } */
+
+ T (1, "%hhi", 0); /* { dg-warning "nul past the end" } */
+ T (1, "%hhi", 1); /* { dg-warning "nul past the end" } */
+ T (1, "%hhi", -1); /* { dg-warning "into a region" } */
+ T (1, "%+hhi", 0); /* { dg-warning "into a region" } */
+ T (1, "%+hhi", 1); /* { dg-warning "into a region" } */
+ T (1, "%-hhi", 0); /* { dg-warning "nul past the end" } */
+
+ T (2, "%hhi", 0);
+ T (2, "%hhi", 1);
+ T (2, "%hhi", 9);
+ T (2, "% hhi", 9); /* { dg-warning "nul past the end" } */
+ T (2, "%+hhi", 9); /* { dg-warning "nul past the end" } */
+ T (2, "%-hhi", 9);
+ T (2, "%hhi", 10); /* { dg-warning "nul past the end" } */
+ T (2, "%hhi", -1); /* { dg-warning "nul past the end" } */
+ T (2, "% hhi", -1); /* { dg-warning "nul past the end" } */
+ T (2, "%+hhi", -1); /* { dg-warning "nul past the end" } */
+ T (2, "%-hhi", -1); /* { dg-warning "nul past the end" } */
+
+ T (2, "%hho", 0);
+ T (2, "%hho", 1);
+ T (2, "%hho", 7);
+ T (2, "%hho", 010); /* { dg-warning "nul past the end" } */
+ T (2, "%hho", 077); /* { dg-warning "nul past the end" } */
+ T (2, "%hho", -1); /* { dg-warning "into a region" } */
+
+ T (2, "%hhx", 0);
+ T (2, "%hhX", 1);
+ T (2, "%hhx", 7);
+ T (2, "%hhX", 8);
+ T (2, "%hhx", -1); /* { dg-warning "nul past the end" } */
+ T (2, "%hhX", 0xf);
+ T (2, "%hhx", 0x10); /* { dg-warning "nul past the end" } */
+ T (2, "%hhX", 0xff); /* { dg-warning "nul past the end" } */
+
+ T (1, "%#hhx", 0); /* { dg-warning "nul past the end" } */
+ T (2, "%#hhx", 0);
+ T (3, "%#hhx", 1); /* { dg-warning "nul past the end" } */
+
+ T (4, "%hhd", 255);
+ T (4, "%hhd", 256);
+ T (4, "%hhd", 0xfff);
+ T (4, "%hhd", 0xffff);
+
+ T (4, "%hhi", 255);
+ T (4, "%hhi", 256);
+ T (4, "%hhi", 0xfff);
+ T (4, "%hhi", 0xffff);
+
+ T (4, "%hhu", -1);
+ T (4, "%hhu", 255);
+ T (4, "%hhu", 256);
+ T (4, "%hhu", 0xfff);
+ T (4, "%hhu", 0xffff);
+
+ T (4, "%#hhx", 0);
+ T (4, "%#hhx", 1);
+ T (4, "%#hhx", -1); /* { dg-warning "nul past the end" } */
+ T (4, "%#hhx", 0xf);
+ T (4, "%#hhx", 0x10); /* { dg-warning "nul past the end" } */
+ T (4, "%#hhx", 0xff); /* { dg-warning "nul past the end" } */
+ T (4, "%#hhx", 0xfff); /* { dg-warning "nul past the end" } */
+
+ T (4, "%hhi %hhi", 0, 0);
+ T (4, "%hhi %hhi", 9, 9);
+ T (4, "%hhi %hhi", 1, 10); /* { dg-warning "nul past the end" } */
+ T (4, "%hhi %hhi", 10, 1); /* { dg-warning "nul past the end" } */
+ T (4, "%hhi %hhi", 11, 12); /* { dg-warning "into a region" } */
+
+ T (5, "%0*hhd %0*hhi", 0, 7, 0, 9);
+ T (5, "%0*hhd %0*hhi", 1, 7, 1, 9);
+ T (5, "%0*hhd %0*hhi", 1, 7, 2, 9);
+ T (5, "%0*hhd %0*hhi", 2, 7, 1, 9);
+ T (5, "%0*hhd %0*hhi", 2, 7, 2, 9); /* { dg-warning "nul past the end" } */
+ T (5, "%0*hhd %0*hhi", 0, 12, 0, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+ T (5, "%0*hhd %0*hhi", 1, 12, 1, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+ T (5, "%0*hhd %0*hhi", 2, 12, 3, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+
+ /* FIXME: Move the boundary test cases into a file of their own that's
+ exercised only on targets with the matching type limits (otherwise
+ they'll fail). */
+#undef MAX
+#define MAX 127
+
+#undef MIN
+#define MIN (-MAX -1)
+
+ T (1, "%hhi", MAX); /* { dg-warning "into a region" } */
+ T (1, "%hhi", MIN); /* { dg-warning "into a region" } */
+ T (1, "%hhi", MAX + 1); /* { dg-warning "into a region" } */
+
+ T (2, "%hhi", MAX + 1); /* { dg-warning "into a region" } */
+ T (2, "%hhi", MAX + 10); /* { dg-warning "into a region" } */
+ T (2, "%hhi", MAX + 100); /* { dg-warning "into a region" } */
+}
+
+/* Exercise the "%hhd", "%hi", "%ho", "%hu", and "%hx" directives
+ with constant arguments. */
+
+void test_sprintf_chk_h_const (void)
+{
+ T (1, "%hu", 0); /* { dg-warning "nul past the end" } */
+ T (1, "%hu", 1); /* { dg-warning "nul past the end" } */
+ T (1, "%hu", -1); /* { dg-warning "into a region" } */
+
+ T (2, "%hi", 0);
+ T (2, "%hi", 1);
+ T (2, "%hi", 9);
+ T (2, "% hi", 9); /* { dg-warning "nul past the end" } */
+ T (2, "%+hi", 9); /* { dg-warning "nul past the end" } */
+ T (2, "%-hi", 9);
+ T (2, "%hi", 10); /* { dg-warning "nul past the end" } */
+ T (2, "%hi", -1); /* { dg-warning "nul past the end" } */
+ T (2, "% hi", -2); /* { dg-warning "nul past the end" } */
+ T (2, "%+hi", -3); /* { dg-warning "nul past the end" } */
+ T (2, "%-hi", -4); /* { dg-warning "nul past the end" } */
+
+ T (2, "%hu", 0);
+ T (2, "%hu", 1);
+ T (2, "%hu", 9);
+ T (2, "%hu", 10); /* { dg-warning "nul past the end" } */
+ T (2, "%hu", -1); /* { dg-warning "into a region" } */
+
+ T (2, "%ho", 0);
+ T (2, "%ho", 1);
+ T (2, "%ho", 7);
+ T (2, "%ho", 010); /* { dg-warning "nul past the end" } */
+ T (2, "%ho", 077); /* { dg-warning "nul past the end" } */
+ T (2, "%ho", 0100); /* { dg-warning "into a region" } */
+ T (2, "%ho", -1); /* { dg-warning "into a region" } */
+
+ T (2, "%hx", 0);
+ T (2, "%hx", 1);
+ T (2, "%hx", 7);
+ T (2, "%hx", 0xf);
+ T (2, "%hx", 0x10); /* { dg-warning "nul past the end" } */
+ T (2, "%hx", 0xff); /* { dg-warning "nul past the end" } */
+ T (2, "%hx", 0x100); /* { dg-warning "into a region" } */
+ T (2, "%hx", -1); /* { dg-warning "into a region" } */
+
+ T (3, "% hi", 7);
+ T (3, "%+hi", 8);
+ T (3, "%-hi", 9);
+ T (3, "%hi", 10);
+ T (3, "%hi", -1);
+ T (3, "% hi", -2);
+ T (3, "%+hi", -3);
+ T (3, "%-hi", -4);
+
+ T (5, "%hu", 9999);
+ T (5, "%hu", 10000); /* { dg-warning "nul past the end" } */
+ T (5, "%hu", 65535); /* { dg-warning "nul past the end" } */
+
+ T (1, "%#hx", 0); /* { dg-warning "nul past the end" } */
+ T (2, "%#hx", 0);
+ T (3, "%#hx", 1); /* { dg-warning "nul past the end" } */
+
+ T (4, "%#hx", 0);
+ T (4, "%#hx", 1);
+ T (4, "%#hx", 0xf);
+ T (4, "%#hx", 0x10); /* { dg-warning "nul past the end" } */
+ T (4, "%#hx", 0xff); /* { dg-warning "nul past the end" } */
+ T (4, "%#hx", 0x100); /* { dg-warning "into a region" } */
+ T (4, "%#hx", -1); /* { dg-warning "into a region" } */
+
+#undef MAX
+#define MAX 65535
+
+ T (1, "%hhu", 0); /* { dg-warning "nul past the end" } */
+ T (1, "%hhu", 1); /* { dg-warning "nul past the end" } */
+ T (1, "%hhu", -1); /* { dg-warning "into a region" } */
+ T (1, "%hhu", MAX); /* { dg-warning "into a region" } */
+ T (1, "%hhu", MAX + 1); /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with
+ constant arguments. */
+
+void test_sprintf_chk_integer_const (void)
+{
+ T ( 1, "%i", 0); /* { dg-warning "nul past the end" } */
+ T ( 1, "%i", 1); /* { dg-warning "nul past the end" } */
+ T ( 1, "%i", -1); /* { dg-warning "into a region" } */
+ T ( 1, "%i_", 1); /* { dg-warning "character ._. at offset 2 past the end" } */
+ T ( 1, "_%i", 1); /* { dg-warning "into a region" } */
+ T ( 1, "_%i_", 1); /* { dg-warning "into a region" } */
+ T ( 1, "%o", 0); /* { dg-warning "nul past the end" } */
+ T ( 1, "%u", 0); /* { dg-warning "nul past the end" } */
+ T ( 1, "%x", 0); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#x", 0); /* { dg-warning "nul past the end" } */
+ T ( 1, "%x", 1); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#x", 1); /* { dg-warning "into a region" } */
+
+ T ( 2, "%i", 0);
+ T ( 2, "%i", 1);
+ T ( 2, "%i", 9);
+ T ( 2, "%i", -1); /* { dg-warning "nul past the end" } */
+ T ( 2, "%i", 10); /* { dg-warning "nul past the end" } */
+ T ( 2, "%i_", 0); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%i", 0); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%i_", 0); /* { dg-warning "character ._. at offset 3 past the end" } */
+ T ( 2, "%o", 1);
+ T ( 2, "%o", 7);
+ T ( 2, "%o", 010); /* { dg-warning "nul past the end" } */
+ T ( 2, "%o", 0100); /* { dg-warning "into a region" } */
+ T ( 2, "%x", 1);
+ T ( 2, "%#x", 1); /* { dg-warning "into a region" } */
+ T ( 2, "%x", 0xa);
+ T ( 2, "%x", 0xf);
+ T ( 2, "%x", 0x10); /* { dg-warning "nul past the end" } */
+ T ( 2, "%x", 0xff); /* { dg-warning "nul past the end" } */
+ T ( 2, "%x", 0x1ff); /* { dg-warning "into a region" } */
+
+ T ( 3, "%i", 0);
+ T ( 3, "%i", 1);
+ T ( 3, "%i", 9);
+ T ( 3, "%i", -9);
+ T ( 3, "%i", 10);
+ T ( 3, "%i", 99);
+ T ( 3, "%i", -99); /* { dg-warning "nul past the end" } */
+
+ /* ~0U is formatted into exactly three bytes as "-1" followed by
+ the terminating NUL character. */
+ T ( 3, "%+i", ~0U);
+ T ( 3, "%-i", ~0U);
+ T ( 3, "% i", ~0U);
+
+ T ( 8, "%8u", 1); /* { dg-warning "nul past the end" } */
+ T ( 9, "%8u", 1);
+
+ T ( 7, "%1$i%2$i%3$i", 1, 23, 456);
+ T ( 8, "%1$i%2$i%3$i%1$i", 1, 23, 456);
+ T ( 8, "%1$i%2$i%3$i%2$i", 1, 23, 456); /* { dg-warning "nul past the end" } */
+ T ( 8, "%1$i%2$i%3$i%3$i", 1, 23, 456); /* { dg-warning "into a region" } */
+
+#undef MAX
+#define MAX 2147483647 /* 10 digits. */
+#undef MIN
+#define MIN (-MAX -1) /* Sign plus 10 digits. */
+
+ T ( 1, "%i", MAX); /* { dg-warning "into a region" } */
+ T ( 1, "%i", MIN); /* { dg-warning "into a region" } */
+ T ( 2, "%i", MAX); /* { dg-warning "into a region" } */
+ T ( 2, "%i", MIN); /* { dg-warning "into a region" } */
+ T (10, "%i", 123456789);
+ T (10, "%i", -123456789); /* { dg-warning "nul past the end" } */
+ T (10, "%i", MAX); /* { dg-warning "nul past the end" } */
+ T (10, "%i", MIN); /* { dg-warning "into a region" } */
+
+ T (11, "%i", MAX);
+ T (11, "%i", MIN); /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%jd", "%ji", "%jo", "%ju", and "%jx" directives
+ for the formatting of intmax_t and uintmax_t values with constant
+ arguments. */
+
+void test_sprintf_chk_j_const (void)
+{
+#define I(x) ((__INTMAX_TYPE__)x)
+
+ T ( 1, "%ji", I ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%ji", I ( 1)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%ji", I ( -1)); /* { dg-warning "into a region" } */
+ T ( 1, "%ji_", I ( 1)); /* { dg-warning "character ._. at offset 3 past the end" } */
+ T ( 1, "_%ji", I ( 1)); /* { dg-warning "into a region" } */
+ T ( 1, "_%ji_",I ( 1)); /* { dg-warning "into a region" } */
+ T ( 1, "%jo", I ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%ju", I ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%jx", I ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#jx", I ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%jx", I ( 1)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#jx", I ( 1)); /* { dg-warning "into a region" } */
+
+ T ( 2, "%ji", I ( 0));
+ T ( 2, "%ji", I ( 1));
+ T ( 2, "%ji", I ( 9));
+ T ( 2, "%ji", I ( -1)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%ji", I ( 10)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%ji_", I ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%ji", I ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%ji_",I ( 0)); /* { dg-warning "character ._. at offset 4 past the end" } */
+ T ( 2, "%jo", I ( 1));
+ T ( 2, "%jo", I ( 7));
+ T ( 2, "%jo", I ( 010)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%jo", I ( 0100)); /* { dg-warning "into a region" } */
+ T ( 2, "%jx", I ( 1));
+ T ( 2, "%#jx", I ( 1)); /* { dg-warning "into a region" } */
+ T ( 2, "%jx", I ( 0xa));
+ T ( 2, "%jx", I ( 0xf));
+ T ( 2, "%jx", I ( 0x10)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%jx", I ( 0xff)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%jx", I (0x1ff)); /* { dg-warning "into a region" } */
+
+ T ( 3, "%ji", I ( 0));
+ T ( 3, "%ji", I ( 1));
+ T ( 3, "%ji", I ( 9));
+ T ( 3, "%ji", I ( -9));
+ T ( 3, "%ji", I ( 10));
+ T ( 3, "%ji", I ( 99));
+ T ( 3, "%ji", I ( -99)); /* { dg-warning "nul past the end" } */
+
+ /* ~0 is formatted into exactly three bytes as "-1" followed by
+ the terminating NUL character. */
+ T ( 3, "%+ji", ~I (0));
+ T ( 3, "%-ji", ~I (0));
+ T ( 3, "% ji", ~I (0));
+
+ T ( 8, "%8ju", I (1)); /* { dg-warning "nul past the end" } */
+ T ( 9, "%8ju", I (1));
+}
+
+/* Exercise the "%ld", "%li", "%lo", "%lu", and "%lx" directives
+ with constant arguments. */
+
+void test_sprintf_chk_l_const (void)
+{
+ T ( 1, "%li", 0L); /* { dg-warning "nul past the end" } */
+ T ( 1, "%li", 1L); /* { dg-warning "nul past the end" } */
+ T ( 1, "%li", -1L); /* { dg-warning "into a region" } */
+ T ( 1, "%li_", 1L); /* { dg-warning "character ._. at offset 3 past the end" } */
+ T ( 1, "_%li", 1L); /* { dg-warning "into a region" } */
+ T ( 1, "_%li_", 1L); /* { dg-warning "into a region" } */
+ T ( 1, "%lo", 0L); /* { dg-warning "nul past the end" } */
+ T ( 1, "%lu", 0L); /* { dg-warning "nul past the end" } */
+ T ( 1, "%lx", 0L); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#lx", 0L); /* { dg-warning "nul past the end" } */
+ T ( 1, "%lx", 1L); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#lx", 1L); /* { dg-warning "into a region" } */
+
+ T ( 2, "%li", 0L);
+ T ( 2, "%li", 1L);
+ T ( 2, "%li", 9L);
+ T ( 2, "%li", -1L); /* { dg-warning "nul past the end" } */
+ T ( 2, "%li", 10L); /* { dg-warning "nul past the end" } */
+ T ( 2, "%li_", 0L); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%li", 0L); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%li_", 0L); /* { dg-warning "character ._. at offset 4 past the end" } */
+ T ( 2, "%lo", 1L);
+ T ( 2, "%lo", 7L);
+ T ( 2, "%lo", 010L); /* { dg-warning "nul past the end" } */
+ T ( 2, "%lo", 0100L); /* { dg-warning "into a region" } */
+ T ( 2, "%lx", 1L);
+ T ( 2, "%#lx", 1L); /* { dg-warning "into a region" } */
+ T ( 2, "%lx", 0xaL);
+ T ( 2, "%lx", 0xfL);
+ T ( 2, "%lx", 0x10L); /* { dg-warning "nul past the end" } */
+ T ( 2, "%lx", 0xffL); /* { dg-warning "nul past the end" } */
+ T ( 2, "%lx", 0x1ffL); /* { dg-warning "into a region" } */
+
+ T ( 3, "%li", 0L);
+ T ( 3, "%li", 1L);
+ T ( 3, "%li", 9L);
+ T ( 3, "%li", -9L);
+ T ( 3, "%li", 10L);
+ T ( 3, "%li", 99L);
+ T ( 3, "%li", -99L); /* { dg-warning "nul past the end" } */
+
+ /* ~0U is formatted into exactly three bytes as "-1" followed by
+ the terminating NUL character. */
+ T ( 3, "%+li", ~0LU);
+ T ( 3, "%-li", ~0LU);
+ T ( 3, "% li", ~0LU);
+
+ T ( 8, "%8lu", 1L); /* { dg-warning "nul past the end" } */
+ T ( 9, "%8lu", 1L);
+}
+
+/* Exercise the "%lld", "%lli", "%llo", "%llu", and "%llx" directives
+ with constant arguments. */
+
+void test_sprintf_chk_ll_const (void)
+{
+ T ( 1, "%lli", 0LL); /* { dg-warning "nul past the end" } */
+ T ( 1, "%lli", 1LL); /* { dg-warning "nul past the end" } */
+ T ( 1, "%lli", -1LL); /* { dg-warning "into a region" } */
+ T ( 1, "%lli_", 1LL); /* { dg-warning "character ._. at offset 4 past the end" } */
+ T ( 1, "_%lli", 1LL); /* { dg-warning "into a region" } */
+ T ( 1, "_%lli_", 1LL); /* { dg-warning "into a region" } */
+ T ( 1, "%llo", 0LL); /* { dg-warning "nul past the end" } */
+ T ( 1, "%llu", 0LL); /* { dg-warning "nul past the end" } */
+ T ( 1, "%llx", 0LL); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#llx", 0LL); /* { dg-warning "nul past the end" } */
+ T ( 1, "%llx", 1LL); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#llx", 1LL); /* { dg-warning "into a region" } */
+
+ T ( 2, "%lli", 0LL);
+ T ( 2, "%lli", 1LL);
+ T ( 2, "%lli", 9LL);
+ T ( 2, "%lli", -1LL); /* { dg-warning "nul past the end" } */
+ T ( 2, "%lli", 10LL); /* { dg-warning "nul past the end" } */
+ T ( 2, "%lli_", 0LL); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%lli", 0LL); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%lli_", 0LL); /* { dg-warning "character ._. at offset 5 past the end" } */
+ T ( 2, "%llo", 1LL);
+ T ( 2, "%llo", 7LL);
+ T ( 2, "%llo", 010LL); /* { dg-warning "nul past the end" } */
+ T ( 2, "%llo", 0100LL); /* { dg-warning "into a region" } */
+ T ( 2, "%llx", 1LL);
+ T ( 2, "%#llx", 1LL); /* { dg-warning "into a region" } */
+ T ( 2, "%llx", 0xaLL);
+ T ( 2, "%llx", 0xfLL);
+ T ( 2, "%llx", 0x10LL); /* { dg-warning "nul past the end" } */
+ T ( 2, "%llx", 0xffLL); /* { dg-warning "nul past the end" } */
+ T ( 2, "%llx", 0x1ffLL); /* { dg-warning "into a region" } */
+
+ T ( 3, "%lli", 0LL);
+ T ( 3, "%lli", 1LL);
+ T ( 3, "%lli", 9LL);
+ T ( 3, "%lli", -9LL);
+ T ( 3, "%lli", 10LL);
+ T ( 3, "%lli", 99LL);
+ T ( 3, "%lli", -99LL); /* { dg-warning "nul past the end" } */
+
+ /* ~0U is formatted into exactly three bytes as "-1" followed by
+ the terminating NUL character. */
+ T ( 3, "%+lli", ~0LLU);
+ T ( 3, "%-lli", ~0LLU);
+ T ( 3, "% lli", ~0LLU);
+
+ T ( 8, "%8llu", 1LL); /* { dg-warning "nul past the end" } */
+ T ( 9, "%8llu", 1LL);
+
+ /* Assume 64-bit long long. */
+#define LLONG_MAX 9223372036854775807LL /* 19 bytes */
+#define LLONG_MIN (-LLONG_MAX - 1) /* 20 bytes */
+
+ T (18, "%lli", LLONG_MIN); /* { dg-warning "into a region" } */
+ T (19, "%lli", LLONG_MIN); /* { dg-warning "into a region" } */
+ T (20, "%lli", LLONG_MIN); /* { dg-warning "nul past the end" } */
+ T (21, "%lli", LLONG_MIN);
+
+ T (18, "%lli", LLONG_MAX); /* { dg-warning "into a region" } */
+ T (19, "%lli", LLONG_MAX); /* { dg-warning "nul past the end" } */
+ T (20, "%lli", LLONG_MAX);
+
+ T (21, "%llo", -1LL); /* { dg-warning "into a region" } */
+ T (22, "%llo", -1LL); /* { dg-warning "nul past the end" } */
+ T (23, "%llo", -1LL);
+
+ T (19, "%llu", -1LL); /* { dg-warning "into a region" } */
+ T (20, "%llu", -1LL); /* { dg-warning "nul past the end" } */
+ T (21, "%llu", -1LL);
+
+ T (15, "%llx", -1LL); /* { dg-warning "into a region" } */
+ T (16, "%llx", -1LL); /* { dg-warning "nul past the end" } */
+ T (17, "%llx", -1LL);
+}
+
+void test_sprintf_chk_L_const (void)
+{
+ T ( 1, "%Li", 0LL); /* { dg-warning "nul past the end" } */
+ T ( 1, "%Li", 1LL); /* { dg-warning "nul past the end" } */
+ T ( 1, "%Li", -1LL); /* { dg-warning "into a region" } */
+ T ( 1, "%Li_", 1LL); /* { dg-warning "character ._. at offset 3 past the end" } */
+ T ( 1, "_%Li", 1LL); /* { dg-warning "into a region" } */
+ T ( 1, "_%Li_", 1LL); /* { dg-warning "into a region" } */
+}
+
+void test_sprintf_chk_z_const (void)
+{
+ T ( 1, "%zi", (size_t)0); /* { dg-warning "nul past the end" } */
+ T ( 1, "%zi", (size_t)1); /* { dg-warning "nul past the end" } */
+ T ( 1, "%zi", (size_t)-1L);/* { dg-warning "into a region" } */
+ T ( 1, "%zi_", (size_t)1); /* { dg-warning "character ._. at offset 3 past the end" } */
+ T ( 1, "_%zi", (size_t)1); /* { dg-warning "into a region" } */
+ T ( 1, "_%zi_", (size_t)1); /* { dg-warning "into a region" } */
+
+ T ( 2, "%zu", (size_t)1);
+ T ( 2, "%zu", (size_t)9);
+ T ( 2, "%zu", (size_t)10); /* { dg-warning "nul past the end" } */
+}
+
+void test_sprintf_chk_e_const (void)
+{
+ T (0, "%E", 0.0); /* { dg-warning "into a region" } */
+ T (0, "%e", 0.0); /* { dg-warning "into a region" } */
+ T ( 1, "%E", 1.0); /* { dg-warning "into a region" } */
+ T ( 1, "%e", 1.0); /* { dg-warning "into a region" } */
+ T ( 2, "%e", 2.0); /* { dg-warning "into a region" } */
+ T ( 3, "%e", 3.0); /* { dg-warning "into a region" } */
+ T (12, "%e", 1.2); /* { dg-warning "nul past the end" } */
+ T (12, "%e", 12.0); /* { dg-warning "nul past the end" } */
+ T (13, "%e", 1.3); /* 1.300000e+00 */
+ T (13, "%E", 13.0); /* 1.300000e+01 */
+ T (13, "%e", 13.0);
+ T (13, "%E", 1.4e+99); /* 1.400000e+99 */
+ T (13, "%e", 1.5e+100); /* { dg-warning "nul past the end" } */
+ T (14, "%E", 1.6e+101); /* 1.600000E+101 */
+ T (14, "%e", -1.7e+102); /* { dg-warning "nul past the end" } */
+ T (15, "%E", -1.8e+103); /* -1.800000E+103 */
+
+ T (16, "%.8e", -1.9e+104); /* { dg-warning "nul past the end" } */
+ T (17, "%.8e", -2.0e+105); /* -2.00000000e+105 */
+
+ T ( 5, "%.0e", 0.0); /* { dg-warning "nul past the end" } */
+ T ( 5, "%.0e", 1.0); /* { dg-warning "nul past the end" } */
+ T ( 6, "%.0e", 1.0);
+}
+
+/* At -Wformat-length level 1 unknown numbers are assumed to have
+ the value one, and unknown strings are assumed to have a zero
+ length. */
+
+void test_sprintf_chk_s_nonconst (int i, const char *s)
+{
+ T (0, "%s", s); /* { dg-warning "nul past the end" } */
+ T (1, "%s", s);
+ T (1, "%.0s", s);
+ T (1, "%.1s", s); /* { dg-warning "nul past the end" } */
+
+ /* The following will definitely write past the end of the buffer,
+ but since at level 1 the length of an unknown string argument
+ is assumed to be zero, it will write the terminating nul past
+ the end (we don't print "past the end" when we're not
+ sure which we can't be with an unknown string. */
+ T (1, "%1s", s); /* { dg-warning "writing a terminating nul past the end" } */
+}
+
+/* Exercise the hh length modifier with all integer specifiers and
+ a non-constant argument. */
+
+void test_sprintf_chk_hh_nonconst (int a)
+{
+ T (0, "%hhd", a); /* { dg-warning "into a region" } */
+ T (0, "%hhi", a); /* { dg-warning "into a region" } */
+ T (0, "%hhu", a); /* { dg-warning "into a region" } */
+ T (0, "%hhx", a); /* { dg-warning "into a region" } */
+
+ T (1, "%hhd", a); /* { dg-warning "nul past the end" } */
+ T (1, "%hhi", a); /* { dg-warning "nul past the end" } */
+ T (1, "%hhu", a); /* { dg-warning "nul past the end" } */
+ T (1, "%hhx", a); /* { dg-warning "nul past the end" } */
+
+ T (1, "% hhd", a); /* { dg-warning "into a region" } */
+ T (1, "% hhi", a); /* { dg-warning "into a region" } */
+ T (1, "%+hhd", a); /* { dg-warning "into a region" } */
+ T (1, "%+hhi", a); /* { dg-warning "into a region" } */
+ T (1, "%-hhd", a); /* { dg-warning "nul past the end" } */
+ T (1, "%-hhi", a); /* { dg-warning "nul past the end" } */
+
+ T (2, "%hhd", a);
+ T (2, "%hhi", a);
+ T (2, "%hho", a);
+ T (2, "%hhu", a);
+ T (2, "%hhx", a);
+
+ T (2, "% hhd", a); /* { dg-warning "nul past the end" } */
+ T (2, "% hhi", a); /* { dg-warning "nul past the end" } */
+ T (2, "% hho", a); /* { dg-warning ". . flag used with .%o." } */
+ T (2, "% hhu", a); /* { dg-warning ". . flag used with .%u." } */
+ T (2, "% hhx", a); /* { dg-warning ". . flag used with .%x." } */
+
+ T (2, "#%hho", a); /* { dg-warning "nul past the end" } */
+ T (2, "#%hhx", a); /* { dg-warning "nul past the end" } */
+
+ T (3, "%2hhd", a);
+ T (3, "%2hhi", a);
+ T (3, "%2hho", a);
+ T (3, "%2hhu", a);
+ T (3, "%2hhx", a);
+
+ /* Exercise cases where the type of the actual argument (whose value
+ and range are unknown) constrain the size of the output and so
+ can be used to avoid what would otherwise be false positives. */
+
+ T (2, "%hhd", (UChar)a);
+ T (2, "%hhi", (UChar)a);
+ T (2, "%-hhi", (UChar)a);
+}
+
+/* Exercise the h length modifier with all integer specifiers and
+ a non-constant argument. */
+
+void test_sprintf_chk_h_nonconst (int a)
+{
+ T (0, "%hd", a); /* { dg-warning "into a region" } */
+ T (0, "%hi", a); /* { dg-warning "into a region" } */
+ T (0, "%hu", a); /* { dg-warning "into a region" } */
+ T (0, "%hx", a); /* { dg-warning "into a region" } */
+
+ T (1, "%hd", a); /* { dg-warning "nul past the end" } */
+ T (1, "%hi", a); /* { dg-warning "nul past the end" } */
+ T (1, "%hu", a); /* { dg-warning "nul past the end" } */
+ T (1, "%hx", a); /* { dg-warning "nul past the end" } */
+
+ T (1, "% hd", a); /* { dg-warning "into a region" } */
+ T (1, "% hi", a); /* { dg-warning "into a region" } */
+ T (1, "%+hd", a); /* { dg-warning "into a region" } */
+ T (1, "%+hi", a); /* { dg-warning "into a region" } */
+ T (1, "%-hd", a); /* { dg-warning "nul past the end" } */
+ T (1, "%-hi", a); /* { dg-warning "nul past the end" } */
+
+ T (2, "%hd", a);
+ T (2, "%hi", a);
+ T (2, "%ho", a);
+ T (2, "%hu", a);
+ T (2, "%hx", a);
+
+ T (2, "% hd", a); /* { dg-warning "nul past the end" } */
+ T (2, "% hi", a); /* { dg-warning "nul past the end" } */
+ T (2, "% ho", a); /* { dg-warning ". . flag used with .%o." } */
+ T (2, "% hu", a); /* { dg-warning ". . flag used with .%u." } */
+ T (2, "% hx", a); /* { dg-warning ". . flag used with .%x." } */
+
+ T (2, "#%ho", a); /* { dg-warning "nul past the end" } */
+ T (2, "#%hx", a); /* { dg-warning "nul past the end" } */
+
+ T (3, "%2hd", a);
+ T (3, "%2hi", a);
+ T (3, "%2ho", a);
+ T (3, "%2hu", a);
+ T (3, "%2hx", a);
+}
+
+/* Exercise all integer specifiers with no modifier and a non-constant
+ argument. */
+
+void test_sprintf_chk_int_nonconst (int a)
+{
+ T (0, "%d", a); /* { dg-warning "into a region" } */
+ T (0, "%i", a); /* { dg-warning "into a region" } */
+ T (0, "%u", a); /* { dg-warning "into a region" } */
+ T (0, "%x", a); /* { dg-warning "into a region" } */
+
+ T (1, "%d", a); /* { dg-warning "nul past the end" } */
+ T (1, "%i", a); /* { dg-warning "nul past the end" } */
+ T (1, "%u", a); /* { dg-warning "nul past the end" } */
+ T (1, "%x", a); /* { dg-warning "nul past the end" } */
+
+ T (1, "% d", a); /* { dg-warning "into a region" } */
+ T (1, "% i", a); /* { dg-warning "into a region" } */
+ T (1, "%+d", a); /* { dg-warning "into a region" } */
+ T (1, "%+i", a); /* { dg-warning "into a region" } */
+ T (1, "%-d", a); /* { dg-warning "nul past the end" } */
+ T (1, "%-i", a); /* { dg-warning "nul past the end" } */
+
+ T (2, "%d", a);
+ T (2, "%i", a);
+ T (2, "%o", a);
+ T (2, "%u", a);
+ T (2, "%x", a);
+
+ T (2, "% d", a); /* { dg-warning "nul past the end" } */
+ T (2, "% i", a); /* { dg-warning "nul past the end" } */
+ T (2, "% o", a); /* { dg-warning ". . flag used with .%o." } */
+ T (2, "% u", a); /* { dg-warning ". . flag used with .%u." } */
+ T (2, "% x", a); /* { dg-warning ". . flag used with .%x." } */
+
+ T (2, "#%o", a); /* { dg-warning "nul past the end" } */
+ T (2, "#%x", a); /* { dg-warning "nul past the end" } */
+
+ T (3, "%2d", a);
+ T (3, "%2i", a);
+ T (3, "%2o", a);
+ T (3, "%2u", a);
+ T (3, "%2x", a);
+}
+
+void test_sprintf_chk_e_nonconst (double d)
+{
+ /* 1.0 is formatted as "1.000000E+00" (i.e., 12 bytes). */
+ T (0, "%E", d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
+ T (0, "%e", d); /* { dg-warning "into a region" } */
+ T ( 1, "%E", d); /* { dg-warning "into a region" } */
+ T ( 1, "%e", d); /* { dg-warning "into a region" } */
+ T ( 2, "%e", d); /* { dg-warning "into a region" } */
+ T ( 3, "%e", d); /* { dg-warning "into a region" } */
+ T (12, "%e", d); /* { dg-warning "past the end" } */
+ T (12, "%e", d); /* { dg-warning "past the end" } */
+ T (13, "%E", d); /* 1.000000E+00 */
+ T (13, "%e", d);
+ T (14, "%E", d);
+ T (14, "%e", d);
+
+ 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 (0, "%F", d); /* { dg-warning "into a region" } */
+ T (0, "%f", d); /* { dg-warning "into a region" } */
+ T ( 1, "%F", d); /* { dg-warning "into a region" } */
+ T ( 1, "%f", d); /* { dg-warning "into a region" } */
+ T ( 2, "%F", d); /* { dg-warning "into a region" } */
+ T ( 2, "%f", d); /* { dg-warning "into a region" } */
+ T ( 3, "%F", d); /* { dg-warning "into a region" } */
+ T ( 3, "%f", d); /* { dg-warning "into a region" } */
+ T ( 4, "%F", d); /* { dg-warning "into a region" } */
+ T ( 4, "%f", d); /* { dg-warning "into a region" } */
+ T ( 5, "%F", d); /* { dg-warning "into a region" } */
+ T ( 5, "%f", d); /* { dg-warning "into a region" } */
+ T ( 6, "%F", d); /* { dg-warning "into a region" } */
+ T ( 6, "%f", d); /* { dg-warning "into a region" } */
+ T ( 7, "%F", d); /* { dg-warning "into a region" } */
+ T ( 7, "%f", d); /* { dg-warning "into a region" } */
+ T ( 8, "%F", d); /* { dg-warning "nul past the end" } */
+ T ( 8, "%f", d); /* { dg-warning "nul past the end" } */
+ T ( 9, "%F", d);
+ T ( 9, "%f", d);
+}
+
+/* Tests for __builtin_vsprintf_chk are the same as those for
+ __builtin_sprintf_chk with non-constant arguments. */
+#undef T
+#define T(size, fmt) \
+ __builtin___vsprintf_chk (buffer (size), 0, objsize (size), fmt, va)
+
+void test_vsprintf_chk_c (__builtin_va_list va)
+{
+ /* Verify the full text of the diagnostic for just the distinct messages
+ and use abbreviations in subsequent test cases. */
+ T (0, "%c"); /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+ T (1, "%c"); /* { dg-warning "writing a terminating nul past the end" } */
+ T (1, "%c"); /* { dg-warning "nul past the end" } */
+ T (2, "%c");
+ T (2, "%2c"); /* { dg-warning "nul past the end" } */
+ T (2, "%3c"); /* { dg-warning "into a region" } */
+ T (2, "%c%c"); /* { dg-warning "nul past the end" } */
+ T (3, "%c%c");
+
+ /* Wide characters. */
+ T (0, "%lc"); /* { dg-warning "nul past the end" } */
+ T (1, "%lc");
+ T (2, "%lc");
+
+ /* The following could result in as few as a single byte and in as many
+ as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+ the write cannot be reliably diagnosed. */
+ T (2, "%lc");
+ T (2, "%1lc");
+ /* Writing some unknown number of bytes into a field two characters wide. */
+ T (2, "%2lc"); /* { dg-warning "nul past the end" } */
+ T (2, "%lc%lc");
+
+ T (3, "%lc%c");
+ /* Here in the best case each argument will format as single character,
+ causing the terminating NUL to be written past the end. */
+ T (3, "%lc%c%c");
+
+}
+
+void test_vsprintf_chk_int (__builtin_va_list va)
+{
+ T (0, "%d"); /* { dg-warning "into a region" } */
+ T (0, "%i"); /* { dg-warning "into a region" } */
+ T (0, "%u"); /* { dg-warning "into a region" } */
+ T (0, "%x"); /* { dg-warning "into a region" } */
+
+ T (1, "%d"); /* { dg-warning "nul past the end" } */
+ T (1, "%i"); /* { dg-warning "nul past the end" } */
+ T (1, "%u"); /* { dg-warning "nul past the end" } */
+ T (1, "%x"); /* { dg-warning "nul past the end" } */
+
+ T (1, "% d"); /* { dg-warning "into a region" } */
+ T (1, "% i"); /* { dg-warning "into a region" } */
+ T (1, "%+d"); /* { dg-warning "into a region" } */
+ T (1, "%+i"); /* { dg-warning "into a region" } */
+ T (1, "%-d"); /* { dg-warning "nul past the end" } */
+ T (1, "%-i"); /* { dg-warning "nul past the end" } */
+
+ T (2, "%d");
+ T (2, "%i");
+ T (2, "%o");
+ T (2, "%u");
+ T (2, "%x");
+
+ T (2, "% d"); /* { dg-warning "nul past the end" } */
+ T (2, "% i"); /* { dg-warning "nul past the end" } */
+ T (2, "% o"); /* { dg-warning ". . flag used with .%o." } */
+ T (2, "% u"); /* { dg-warning ". . flag used with .%u." } */
+ T (2, "% x"); /* { dg-warning ". . flag used with .%x." } */
+
+ T (2, "#%o"); /* { dg-warning "nul past the end" } */
+ T (2, "#%x"); /* { dg-warning "nul past the end" } */
+
+ T (3, "%2d");
+ T (3, "%2i");
+ T (3, "%2o");
+ T (3, "%2u");
+ T (3, "%2x");
+}
+
+#undef T
+#define T(size, fmt, ...) \
+ __builtin_snprintf (buffer (size), objsize (size), fmt, __VA_ARGS__)
+
+void test_snprintf_c_const (void)
+{
+ /* Verify the full text of the diagnostic for just the distinct messages
+ and use abbreviations in subsequent test cases. */
+
+ /* A call to snprintf with a buffer of zero size is a request to determine
+ the size of output without writing anything into the destination. No
+ warning must be issued. */
+ T (0, "%c", 0);
+ T (1, "%c", 0); /* { dg-warning "output truncated before the last format character" } */
+ T (1, "%c", '1'); /* { dg-warning "output truncated" } */
+ T (2, "%c", '1');
+ T (2, "%2c", '1'); /* { dg-warning "output truncated" } */
+ T (2, "%3c", '1'); /* { dg-warning "directive output truncated" } */
+ T (2, "%c%c", '1', '2'); /* { dg-warning "output truncated" } */
+ T (3, "%c%c", '1', '2');
+
+ /* Wide characters. */
+ T (0, "%lc", 0);
+ T (1, "%lc", 0);
+ T (2, "%lc", 0);
+
+ /* The following could result in as few as a single byte and in as many
+ as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+ the write cannot be reliably diagnosed. */
+ T (2, "%lc", L'1');
+ T (2, "%1lc", L'1');
+ /* Writing at least 1 characted into a field two characters wide. */
+ T (2, "%2lc", L'1'); /* { dg-warning "output truncated before the last format character" } */
+
+ T (3, "%lc%c", L'1', '2');
+ /* Here in the best case each argument will format as single character,
+ causing the output to be truncated just before the terminating NUL
+ (i.e., cutting off the '3'). */
+ T (3, "%lc%c%c", L'1', '2', '3'); /* { dg-warning "output truncated" } */
+ T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+#undef T
+#define T(size, fmt, ...) \
+ __builtin___snprintf_chk (buffer (size), objsize (size), \
+ 0, objsize (size), fmt, __VA_ARGS__)
+
+void test_snprintf_chk_c_const (void)
+{
+ /* Verify that specifying a size of the destination buffer that's
+ bigger than its actual size (normally determined and passed to
+ the function by __builtin_object_size) is diagnosed. */
+ __builtin___snprintf_chk (buffer, 3, 0, 2, " "); /* { dg-warning "always overflow|specified size 3 exceeds the size 2 of the destination" } */
+
+ T (0, "%c", 0);
+ T (0, "%c%c", 0, 0);
+ T (0, "%c_%c", 0, 0);
+ T (0, "_%c_%c", 0, 0);
+
+ T (1, "%c", 0); /* { dg-warning "output truncated before the last format character" } */
+ T (1, "%c", '1'); /* { dg-warning "output truncated" } */
+ T (2, "%c", '1');
+ T (2, "%2c", '1'); /* { dg-warning "output truncated" } */
+ T (2, "%3c", '1'); /* { dg-warning "directive output truncated" } */
+ T (2, "%c%c", '1', '2'); /* { dg-warning "output truncated before the last format character" } */
+ T (3, "%c%c", '1', '2');
+ T (3, "%c_%c", '1', '2'); /* { dg-warning "output truncated" } */
+
+ /* Wide characters. */
+ T (0, "%lc", 0);
+ T (1, "%lc", 0);
+ T (2, "%lc", 0);
+
+ /* The following could result in as few as a single byte and in as many
+ as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+ the write cannot be reliably diagnosed. */
+ T (2, "%lc", L'1');
+ T (2, "%1lc", L'1');
+ /* Writing at least 1 characted into a field two characters wide. */
+ T (2, "%2lc", L'1'); /* { dg-warning "output truncated before the last format character" } */
+
+ T (3, "%lc%c", L'1', '2');
+ /* Here in the best case each argument will format as single character,
+ causing the output to be truncated just before the terminating NUL
+ (i.e., cutting off the '3'). */
+ T (3, "%lc%c%c", L'1', '2', '3'); /* { dg-warning "output truncated" } */
+ T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+/* Macro to verify that calls to __builtin_vsprintf (i.e., with no size
+ argument) issue diagnostics by correctly determining the size of
+ the destination buffer. */
+#undef T
+#define T(size, fmt) \
+ __builtin_vsprintf (buffer (size), fmt, va)
+
+void test_vsprintf_s (__builtin_va_list va)
+{
+ T (0, "%s"); /* { dg-warning "writing a terminating nul past the end" } */
+ T (1, "%s");
+ T (1, "%1s"); /* { dg-warning "writing a terminating nul past the end" } */
+
+ T (2, "%s%s");
+ T (2, "%s%s_");
+ T (2, "%s_%s");
+ T (2, "_%s%s");
+ T (2, "_%s_%s"); /* { dg-warning "writing a terminating nul past the end" } */
+}
+
+/* Exercise all integer specifiers with no modifier and a non-constant
+ argument. */
+
+void test_vsprintf_int (__builtin_va_list va)
+{
+ T (0, "%d"); /* { dg-warning "into a region" } */
+ T (0, "%i"); /* { dg-warning "into a region" } */
+ T (0, "%u"); /* { dg-warning "into a region" } */
+ T (0, "%x"); /* { dg-warning "into a region" } */
+
+ T (1, "%d"); /* { dg-warning "nul past the end" } */
+ T (1, "%i"); /* { dg-warning "nul past the end" } */
+ T (1, "%u"); /* { dg-warning "nul past the end" } */
+ T (1, "%x"); /* { dg-warning "nul past the end" } */
+
+ T (1, "% d"); /* { dg-warning "into a region" } */
+ T (1, "% i"); /* { dg-warning "into a region" } */
+ T (1, "%+d"); /* { dg-warning "into a region" } */
+ T (1, "%+i"); /* { dg-warning "into a region" } */
+ T (1, "%-d"); /* { dg-warning "nul past the end" } */
+ T (1, "%-i"); /* { dg-warning "nul past the end" } */
+
+ T (2, "%d");
+ T (2, "%i");
+ T (2, "%o");
+ T (2, "%u");
+ T (2, "%x");
+
+ T (2, "% d"); /* { dg-warning "nul past the end" } */
+ T (2, "% i"); /* { dg-warning "nul past the end" } */
+ T (2, "% o"); /* { dg-warning ". . flag used with .%o." } */
+ T (2, "% u"); /* { dg-warning ". . flag used with .%u." } */
+ T (2, "% x"); /* { dg-warning ". . flag used with .%x." } */
+
+ T (2, "#%o"); /* { dg-warning "nul past the end" } */
+ T (2, "#%x"); /* { dg-warning "nul past the end" } */
+
+ T (3, "%2d");
+ T (3, "%2i");
+ T (3, "%2o");
+ T (3, "%2u");
+ T (3, "%2x");
+}
+
+#undef T
+#define T(size, fmt) \
+ __builtin_vsnprintf (buffer (size), objsize (size), fmt, va)
+
+void test_vsnprintf_s (__builtin_va_list va)
+{
+ T (0, "%s");
+ T (1, "%s");
+ T (1, "%1s"); /* { dg-warning "output truncated before the last format character" } */
+
+ T (2, "%s%s");
+ T (2, "%s%s_");
+ T (2, "%s_%s");
+ T (2, "_%s%s");
+ T (2, "_%s_%s"); /* { dg-warning "output truncated before the last format character" } */
+}
+
+#undef T
+#define T(size, fmt) \
+ __builtin___vsnprintf_chk (buffer (size), objsize (size), \
+ 0, objsize (size), fmt, va)
+
+void test_vsnprintf_chk_s (__builtin_va_list va)
+{
+ /* Verify that specifying a size of the destination buffer that's
+ bigger than its actual size (normally determined and passed to
+ the function by __builtin_object_size) is diagnosed. */
+ __builtin___snprintf_chk (buffer, 123, 0, 122, " "); /* { dg-warning "always overflow|specified size 123 exceeds the size 122 of the destination object" } */
+
+ __builtin___snprintf_chk (buffer, __SIZE_MAX__, 0, 2, " "); /* { dg-warning "always overflow|destination size .\[0-9\]+. too large" } */
+
+ T (0, "%s");
+ T (1, "%s");
+ T (1, "%1s"); /* { dg-warning "output truncated before the last format character" } */
+
+ T (2, "%s%s");
+ T (2, "%s%s_");
+ T (2, "%s_%s");
+ T (2, "_%s%s");
+ T (2, "_%s_%s"); /* { dg-warning "output truncated before the last format character" } */
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,207 @@
+/* { 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 two tests, since the length of the strings isn't
+ known, at level 2 a length of at least 1 is assumed. */
+ T (1, "%-s", a->a2); /* { dg-warning "writing a terminating nul" } */
+ T (1, "%-s", a->a3); /* { dg-warning "writing between 1 and 2 bytes into a region of size 1" } */
+
+ /* 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);
+}
new file mode 100644
@@ -0,0 +1,234 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+#ifndef LINE
+# define LINE 0
+#endif
+
+#define bos(x) __builtin_object_size (x, 0)
+
+#define T(bufsize, fmt, ...) \
+ do { \
+ if (!LINE || __LINE__ == LINE) \
+ { \
+ char *d = (char *)__builtin_malloc (bufsize); \
+ __builtin___sprintf_chk (d, 0, bos (d), fmt, __VA_ARGS__); \
+ sink (d); \
+ } \
+ } while (0)
+
+void
+sink (void*);
+
+/* Identity function to verify that the checker figures out the value
+ of the operand even when it's not constant (i.e., makes use of
+ inlining and constant propagation information). */
+
+int i (int x) { return x; }
+const char* s (const char *str) { return str; }
+
+/* Function to "generate" a unique unknown number (as far as GCC can
+ tell) each time it's called. It prevents the optimizer from being
+ able to narrow down the ranges of possible values in test functions
+ with repeated references to the same variable. */
+extern int x (void);
+
+/* Verify that the checker can detect buffer overflow when the "%s"
+ argument is in a known range of lengths and one or both of which
+ exceed the size of the destination. */
+
+void test_sprintf_chk_string (const char *s, const char *t)
+{
+#define x x ()
+
+ T (1, "%s", x ? "" : "1"); /* { dg-warning "nul past the end" } */
+ T (1, "%s", x ? "1" : ""); /* { dg-warning "nul past the end" } */
+ T (1, "%s", x ? s : "1"); /* { dg-warning "nul past the end" } */
+ T (1, "%s", x ? "1" : s); /* { dg-warning "nul past the end" } */
+ T (1, "%s", x ? s : t);
+
+ T (2, "%s", x ? "" : "1");
+ T (2, "%s", x ? "" : s);
+ T (2, "%s", x ? "1" : "");
+ T (2, "%s", x ? s : "");
+ T (2, "%s", x ? "1" : "2");
+ T (2, "%s", x ? "" : "12"); /* { dg-warning "nul past the end" } */
+ T (2, "%s", x ? "12" : ""); /* { dg-warning "nul past the end" } */
+
+ T (2, "%s", x ? "" : "123"); /* { dg-warning "into a region" } */
+ T (2, "%s", x ? "123" : ""); /* { dg-warning "into a region" } */
+
+#undef x
+}
+
+
+/* Verify that the checker makes use of integer constant propagation
+ to detect buffer overflow in non-constant cases. */
+
+void test_sprintf_chk_integer_value (void)
+{
+ T ( 1, "%i", i ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%i", i ( 1)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%i", i ( -1)); /* { dg-warning "into a region" } */
+ T ( 1, "%i_", i ( 1)); /* { dg-warning "character ._. at offset 2 past the end" } */
+ T ( 1, "_%i", i ( 1)); /* { dg-warning "into a region" } */
+ T ( 1, "_%i_",i ( 1)); /* { dg-warning "into a region" } */
+ T ( 1, "%o", i ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%u", i ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%x", i ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#x", i ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%x", i ( 1)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%#x", i ( 1)); /* { dg-warning "into a region" } */
+
+ T ( 2, "%i", i ( 0));
+ T ( 2, "%i", i ( 1));
+ T ( 2, "%i", i ( 9));
+ T ( 2, "%i", i ( -1)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%i", i ( 10)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%i_", i ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%i", i ( 0)); /* { dg-warning "nul past the end" } */
+ T ( 2, "_%i_",i ( 0)); /* { dg-warning "character ._. at offset 3 past the end" } */
+ T ( 2, "%o", i ( 1));
+ T ( 2, "%o", i ( 7));
+ T ( 2, "%o", i ( 010)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%o", i ( 0100)); /* { dg-warning "into a region" } */
+ T ( 2, "%x", i ( 1));
+ T ( 2, "%#x", i ( 1)); /* { dg-warning "into a region" } */
+ T ( 2, "%x", i ( 0xa));
+ T ( 2, "%x", i ( 0xf));
+ T ( 2, "%x", i ( 0x10)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%x", i ( 0xff)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%x", i (0x1ff)); /* { dg-warning "into a region" } */
+
+ T ( 3, "%i", i ( 0));
+ T ( 3, "%i", i ( 1));
+ T ( 3, "%i", i ( 9));
+ T ( 3, "%i", i ( -9));
+ T ( 3, "%i", i ( 10));
+ T ( 3, "%i", i ( 99));
+ T ( 3, "%i", i ( -99)); /* { dg-warning "nul past the end" } */
+
+ T ( 3, "%i", i (99) + i (1)); /* { dg-warning "nul past the end" } */
+
+ T ( 8, "%8u", i ( 1)); /* { dg-warning "nul past the end" } */
+ T ( 9, "%8u", i ( 1));
+}
+
+/* Functions to require optimization to figure out the range of the operand.
+ Used to verify that the checker makes use of the range information to
+ avoid diagnosing the output of sufficiently constrained arguments to
+ integer directives. */
+
+signed char*
+range_schar (signed char *val, signed char min, signed char max)
+{
+ if (*val < min || max < *val) __builtin_abort ();
+ return val;
+}
+
+unsigned char*
+range_uchar (unsigned char *val, unsigned char min, unsigned char max)
+{
+ if (*val < min || max < *val) __builtin_abort ();
+ return val;
+}
+
+signed short*
+range_sshort (signed short *val, signed short min, signed short max)
+{
+ if (*val < min || max < *val) __builtin_abort ();
+ return val;
+}
+
+unsigned short*
+range_ushort (unsigned short *val, unsigned short min, unsigned short max)
+{
+ if (*val < min || max < *val) __builtin_abort ();
+ return val;
+}
+
+/* Helper to prevent GCC from figuring out the return value. */
+extern int idx (void);
+
+/* Exercise ranges only in types signed and unsigned char and short.
+ No other types work due to bug 71690. */
+
+void test_sprintf_chk_range_schar (signed char *a)
+{
+ (void)&a;
+
+ /* Ra creates a range of signed char for A [idx]. A different
+ value is used each time to prevent the ranges from intesecting
+ one another, possibly even eliminating some tests as a result
+ of the range being empty. */
+#define R(min, max) *range_schar (a + idx (), min, max)
+
+ T ( 0, "%i", R (0, 9)); /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+ T ( 1, "%i", R (0, 9)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%i", R (0, 9));
+ T ( 2, "%i", R (-1, 0)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+ T ( 2, "%i", R (9, 10)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+ T ( 3, "%i", R ( -9, 9));
+ T ( 3, "%i", R (-99, 99)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+ T ( 3, "%i", R ( 0, 99));
+ T ( 3, "%i", R ( 0, 100)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+ /* The following call may write as few as 3 bytes and as many as 5.
+ It's judgment call how best to diagnose it to make the potential
+ problem clear. */
+ T ( 3, "%i%i", R (1, 10), R (9, 10)); /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+
+ T ( 4, "%i%i", R (10, 11), R (12, 13)); /* { dg-warning "nul past the end" } */
+
+ T ( 5, "%i%i", R (-9, 99), R (-9, 99));
+
+ T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 9));
+ T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 10)); /* { dg-warning "may write a terminating nul past the end" } */
+ T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 9)); /* { dg-warning "may write a terminating nul past the end" } */
+ T ( 6, "%i_%i_%i", R (0, 10), R (0, 9), R (0, 9)); /* { dg-warning "may write a terminating nul past the end" } */
+ T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+}
+
+void test_sprintf_chk_range_uchar (unsigned char *a, unsigned char *b)
+{
+ (void)&a;
+ (void)&b;
+
+#undef Ra
+#define Ra(min, max) *range_uchar (a + idx (), min, max)
+
+ T ( 0, "%i", Ra (0, 9)); /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+ T ( 1, "%i", Ra (0, 9)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%i", Ra (0, 9));
+ T ( 2, "%i", Ra (9, 10)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+ T ( 3, "%i", Ra (0, 99));
+ T ( 3, "%i", Ra (0, 100)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
+
+void test_sprintf_chk_range_sshort (signed short *a, signed short *b)
+{
+ (void)&a;
+ (void)&b;
+
+#undef Ra
+#define Ra(min, max) *range_sshort (a + idx (), min, max)
+
+ T ( 0, "%i", Ra ( 0, 9)); /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+ T ( 1, "%i", Ra ( 0, 1)); /* { dg-warning "nul past the end" } */
+ T ( 1, "%i", Ra ( 0, 9)); /* { dg-warning "nul past the end" } */
+ T ( 2, "%i", Ra ( 0, 1));
+ T ( 2, "%i", Ra ( 8, 9));
+ T ( 2, "%i", Ra ( 0, 9));
+ T ( 2, "%i", Ra (-1, 0)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+ T ( 2, "%i", Ra ( 9, 10)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+ T ( 3, "%i", Ra ( 0, 99));
+ T ( 3, "%i", Ra (99, 999)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+ T ( 4, "%i", Ra ( 0, 999));
+ T ( 4, "%i", Ra ( 99, 999));
+ T ( 4, "%i", Ra (998, 999));
+ T ( 4, "%i", Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
new file mode 100644
@@ -0,0 +1,435 @@
+/* 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 enable 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 -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, char *dst, const char *fmt,
+ __builtin_va_list va)
+{
+ int n = __builtin_vsprintf (dst, fmt, va);
+ size_t len = __builtin_strlen (dst);
+
+ ++ntests;
+
+ int fail = 0;
+ if (res != n)
+ {
+ __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" "
+ "doesn't match function call return value: %i != %i\n",
+ func, line, fmt, dst, res, n);
+ fail = 1;
+ }
+ else
+ {
+ __builtin_printf ("PASS: %s:%i: \"%s\" result %i: \"%s\"\n",
+ func, line, fmt, res, dst);
+
+ if ((size_t)res != len)
+ {
+ __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" "
+ "doesn't match output length: %i != %zu\n",
+ func, line, fmt, dst, res, len);
+ fail = 1;
+ }
+ }
+
+ if (fail)
+ ++nfails;
+}
+
+void __attribute__ ((noclone, noinline))
+check (const char *func, int line, int res, char *dst, const char *fmt, ...)
+{
+ __builtin_va_list va;
+ __builtin_va_start (va, fmt);
+ checkv (func, line, res, dst, fmt, va);
+ __builtin_va_end (va);
+}
+
+char buffer[256];
+char* volatile dst = 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, res, n) \
+ 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, res, n) \
+ __builtin_printf ("FAIL: %s:%i: expected %i, got %i\n", \
+ __func__, line, res, n)
+#endif
+
+#define T(res, fmt, ...) \
+ if (!LINE || LINE == __LINE__) \
+ do { \
+ int n = __builtin_sprintf (buffer + sizeof buffer - res - 1, \
+ fmt, __VA_ARGS__); \
+ if (res != n) \
+ { \
+ TEST_FAILURE (__LINE__, res, n); \
+ } \
+ check (__func__, __LINE__, res, dst, fmt, __VA_ARGS__); \
+ } while (0)
+
+
+static void __attribute__ ((noinline, noclone))
+test_c (char c)
+{
+ T (1, "%c", '1');
+ T (1, "%c", c);
+ T (2, "%2c", c);
+ T (2, "%c%c", '1', '2');
+ T (3, "%3c", c);
+ T (3, "%c%c%c", '1', '2', '3');
+ T (4, "%c%c %c", '1', '2', '3');
+ T (5, "%c %c %c", '1', '2', '3');
+ T (5, "%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)
+{
+ T ( 1, "%d", 0);
+ T ( 2, "%d%d", 0, 1);
+ T ( 3, "%d%d", 9, 10);
+ T ( 4, "%d%d", 11, 12);
+ T ( 5, "%d:%d", 12, 34);
+ T ( 5, "%d", 12345);
+ T ( 6, "%d", -12345);
+ T (15, "%d:%d:%d:%d", 123, 124, 125, 126);
+
+ T ( 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].
+ T ( 2, "%i", uchar_range (10, 99));
+ T ( 3, "%i", uchar_range (100, 199));
+ */
+
+#if __SIZEOF_INT__ == 2
+ T ( 6, "%6d", i);
+ T ( 6, "%+6d", i);
+ T ( 6, "%-6d", i);
+ T ( 6, "%06d", i);
+#elif __SIZEOF_INT__ == 4
+ T (11, "%11d", i);
+ T (11, "%+11d", i);
+ T (11, "%-11d", i);
+ T (11, "%011d", i);
+#elif __SIZEOF_INT__ == 8
+ T (20, "%20d", i);
+ T (20, "%+20d", i);
+ T (20, "%-29d", i);
+ T (20, "%020d", i);
+#endif
+
+#if __SIZEOF_LONG__ == 2
+ T ( 6, "%6ld", li);
+ T ( 6, "%+6ld", li);
+ T ( 6, "%-6ld", li);
+ T ( 6, "%06ld", li);
+#elif __SIZEOF_LONG__ == 4
+ T (11, "%11ld", li);
+ T (11, "%+11ld", li);
+ T (11, "%-11ld", li);
+ T (11, "%011ld", li);
+#elif __SIZEOF_LONG__ == 8
+ T (20, "%20ld", li);
+ T (20, "%+20ld", li);
+ T (20, "%-20ld", li);
+ T (20, "%020ld", li);
+#endif
+}
+
+static void __attribute__ ((noinline, noclone))
+test_x (unsigned char uc, unsigned short us, unsigned ui)
+{
+ T ( 1, "%hhx", 0);
+ T ( 2, "%2hhx", 0);
+ T ( 2, "%02hhx", 0);
+ T ( 2, "%#02hhx", 0);
+
+ T ( 1, "%hhx", 1);
+ T ( 2, "%2hhx", 1);
+ T ( 2, "%02hhx", 1);
+ T ( 3, "%#02hhx", 1);
+
+ T ( 2, "%2hhx", uc);
+ T ( 2, "%02hhx", uc);
+ T ( 5, "%#05hhx", uc);
+
+ T ( 2, "%2hhx", us);
+ T ( 2, "%02hhx", us);
+ T ( 5, "%#05hhx", us);
+
+ T ( 2, "%2hhx", ui);
+ T ( 2, "%02hhx", ui);
+ T ( 5, "%#05hhx", ui);
+
+ T ( 1, "%x", 0);
+ T ( 1, "%#x", 0);
+ T ( 1, "%#0x", 0);
+ T ( 1, "%x", 1);
+ T ( 1, "%x", 0xf);
+ T ( 2, "%x", 0x10);
+ T ( 2, "%x", 0xff);
+ T ( 3, "%x", 0x100);
+
+ T (11, "%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).
+ T (11, "%02x:%02x:%02x:%02x", uc, uc, uc, uc);
+ */
+ T (11, "%02hhx:%02hhx:%02hhx:%02hhx", uc, uc, uc, uc);
+
+#if __SIZEOF_SHORT__ == 2
+ T ( 4, "%04hx", us);
+ T ( 9, "%04hx:%04hx", us, us);
+ T (14, "%04hx:%04hx:%04hx", us, us, us);
+ T (19, "%04hx:%04hx:%04hx:%04hx", us, us, us, us);
+#endif
+
+#if __SIZEOF_INT__ == 2
+ T ( 4, "%04x", ui);
+ T ( 6, "%#06x", ui);
+#elif __SIZEOF_INT__ == 4
+ T ( 8, "%08x", ui);
+ T (10, "%#010x", ui);
+#elif __SIZEOF_INT__ == 8
+ T (16, "%016x", ui);
+ T (18, "%#018x", ui);
+#endif
+}
+
+static void __attribute__ ((noinline, noclone))
+test_a_double ()
+{
+ T ( 6, "%a", 0.0); /* 0x0p+0 */
+ T ( 6, "%a", 1.0); /* 0x8p-3 */
+ T ( 6, "%a", 2.0); /* 0x8p-2 */
+
+ T ( 8, "%.1a", 3.0); /* 0xc.0p-2 */
+ T ( 9, "%.2a", 4.0); /* 0xa.00p-1 */
+}
+
+static void __attribute__ ((noinline, noclone))
+test_a_long_double ()
+{
+ T ( 6, "%La", 0.0L); /* 0x0p+0 */
+ T ( 6, "%La", 1.0L); /* 0x8p-3 */
+ T ( 6, "%La", 2.0L); /* 0x8p-2 */
+
+ T ( 8, "%.1La", 3.0L); /* 0xc.0p-2 */
+ T ( 9, "%.2La", 4.0L); /* 0xa.00p-1 */
+}
+
+static void __attribute__ ((noinline, noclone))
+test_e_double ()
+{
+ T (12, "%e", 1.0e0);
+ T (13, "%e", -1.0e0);
+ T (12, "%e", 1.0e+1);
+ T (13, "%e", -1.0e+1);
+ T (12, "%e", 1.0e+12);
+ T (13, "%e", -1.0e+12);
+ T (13, "%e", 1.0e+123);
+ T (14, "%e", -1.0e+123);
+
+ T (12, "%e", 9.999e+99);
+ T (12, "%e", 9.9999e+99);
+ T (12, "%e", 9.99999e+99);
+ T (12, "%e", 9.999999e+99);
+ T (12, "%e", 9.9999994e+99);
+ T (12, "%e", 9.9999995e+99); /* rounded down to "9.999999e+99" */
+ T (13, "%e", 9.9999996e+99); /* rounded up to "1.000000e+100" */
+ T (13, "%e", 9.9999997e+99); /* same */
+ T (13, "%e", 9.9999998e+99); /* same */
+ T (13, "%e", 9.9999999e+99); /* same */
+
+ T (12, "%e", 1.0e-1);
+ T (12, "%e", 1.0e-12);
+ T (13, "%e", 1.0e-123);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_e_long_double ()
+{
+ T (12, "%Le", 1.0e0L);
+ T (13, "%Le", -1.0e0L);
+ T (12, "%Le", 1.0e+1L);
+ T (13, "%Le", -1.0e+1L);
+ T (12, "%Le", 1.0e+12L);
+ T (13, "%Le", -1.0e+12L);
+ T (13, "%Le", 1.0e+123L);
+ T (14, "%Le", -1.0e+123L);
+
+ T (12, "%Le", 9.999e+99L);
+ T (12, "%Le", 9.9999e+99L);
+ T (12, "%Le", 9.99999e+99L);
+ T (12, "%Le", 9.999999e+99L);
+ T (12, "%Le", 9.9999994e+99L);
+ T (13, "%Le", 9.9999995e+99L); /* rounded up to "1.000000e+100" */
+ T (13, "%Le", 9.9999996e+99L); /* same */
+ T (13, "%Le", 9.9999997e+99L); /* same */
+ T (13, "%Le", 9.9999998e+99L); /* same */
+ T (13, "%Le", 9.9999999e+99L); /* same */
+
+ T (12, "%Le", 1.0e-1L);
+ T (12, "%Le", 1.0e-12L);
+ T (13, "%Le", 1.0e-123L);
+
+ T ( 6, "%.0Le", 1.0e-111L);
+ T ( 8, "%.1Le", 1.0e-111L);
+ T (19, "%.12Le", 1.0e-112L);
+ T (20, "%.13Le", 1.0e-113L);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_f_double ()
+{
+ T ( 8, "%f", 0.0e0);
+ T ( 8, "%f", 0.1e0);
+ T ( 8, "%f", 0.12e0);
+ T ( 8, "%f", 0.123e0);
+ T ( 8, "%f", 0.1234e0);
+ T ( 8, "%f", 0.12345e0);
+ T ( 8, "%f", 0.123456e0);
+ T ( 8, "%f", 1.234567e0);
+
+ T ( 9, "%f", 1.0e+1);
+ T ( 20, "%f", 1.0e+12);
+ T (130, "%f", 1.0e+123);
+
+ T ( 8, "%f", 1.0e-1);
+ T ( 8, "%f", 1.0e-12);
+ T ( 8, "%f", 1.0e-123);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_f_long_double ()
+{
+ T ( 8, "%Lf", 0.0e0L);
+ T ( 8, "%Lf", 0.1e0L);
+ T ( 8, "%Lf", 0.12e0L);
+ T ( 8, "%Lf", 0.123e0L);
+ T ( 8, "%Lf", 0.1234e0L);
+ T ( 8, "%Lf", 0.12345e0L);
+ T ( 8, "%Lf", 0.123456e0L);
+ T ( 8, "%Lf", 1.234567e0L);
+
+ T ( 9, "%Lf", 1.0e+1L);
+ T ( 20, "%Lf", 1.0e+12L);
+ T (130, "%Lf", 1.0e+123L);
+
+ T ( 8, "%Lf", 1.0e-1L);
+ T ( 8, "%Lf", 1.0e-12L);
+ T ( 8, "%Lf", 1.0e-123L);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_s (void)
+{
+ T ( 0, "%s", "");
+ T ( 0, "%s", "\0");
+ T ( 1, "%1s", "");
+ T ( 1, "%s", "1");
+ T ( 2, "%2s", "");
+ T ( 2, "%s", "12");
+ T ( 2, "%s%s", "12", "");
+ T ( 2, "%s%s", "", "12");
+ T ( 2, "%s%s", "1", "2");
+ T ( 3, "%3s", "");
+ T ( 3, "%3s", "1");
+ T ( 3, "%3s", "12");
+ T ( 3, "%3s", "123");
+ T ( 3, "%3.3s", "1");
+ T ( 3, "%3.3s", "12");
+ T ( 3, "%3.3s", "123");
+ T ( 3, "%3.3s", "1234");
+ T ( 3, "%3.3s", "12345");
+ T ( 3, "%s %s", "1", "2");
+ T ( 4, "%s %s", "12", "3");
+ T ( 5, "%s %s", "12", "34");
+ T ( 5, "[%s %s]", "1", "2");
+ T ( 6, "[%s %s]", "12", "3");
+ T ( 7, "[%s %s]", "12", "34");
+}
+
+int main ()
+{
+ test_c ('a');
+ test_d_i (0xdeadbeef, 0xdeadbeefL);
+ test_x ('a', 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 ();
+
+ if (nfails)
+ {
+ __builtin_printf ("%u out of %u tests failed\n", nfails, ntests);
+ __builtin_abort ();
+ }
+
+ return 0;
+}
@@ -469,6 +469,7 @@ extern simple_ipa_opt_pass *make_pass_ipa_oacc (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_oacc_kernels (gcc::context *ctxt);
extern gimple_opt_pass *make_pass_gen_hsail (gcc::context *ctxt);
extern gimple_opt_pass *make_pass_warn_nonnull_compare (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_sprintf_length (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);