PR middle-end/49905 - Better sanity checking on sprintf src & dest
to produce warning for dodgy code
gcc/c/ChangeLog:
2016-07-01 Martin Sebor <msebor@redhat.com>
PR middle-end/49905
* c-lang.c (LANG_HOOKS_CHECK_FORMAT_LENGTH): Define.
* c-typeck.c (check_function_arguments): Add an argument.
gcc/c-family/ChangeLog:
2016-07-01 Martin Sebor <msebor@redhat.com>
PR middle-end/49905
* c-common.c (check_function_arguments): Add an argument and pass
it to check_function_format.
* c-common.h (check_function_arguments): Add an argument.
* c-format.c (function_format_info): Add new members.
(conversion_spec): New struct.
(check_format_length, ilog, tree_ilog, format_integer, format_floating,
format_string, bytes_remaining): New functions.
(print_char_table): Add initializers.
(format_check_results): Add members.
(check_function_format): Add an argument.
(check_format_info): Add an argument.
(check_format_info_main): Track the length of the formatted output
and diagnose buffer overflow and truncation.
(check_format_length): Handle -Wformat-length.
(format_type_warning): Avoid bogus warnings when called during
expansion.
* c-format.h (format_char_info): Add members.
* c.opt (-Wformat-legth=): Add new option.
gcc/cp/ChangeLog:
2016-07-01 Martin Sebor <msebor@redhat.com>
PR middle-end/49905
* call.c (build_over_call): Pass function decl to
check_function_arguments.
* typeck.c (cp_build_function_call_vec): Same.
* cp/cp-lang.c: Define LANG_HOOKS_CHECK_FORMAT_LENGTH.
gcc/testsuite/ChangeLog:
2016-07-01 Martin Sebor <msebor@redhat.com>
PR middle-end/49905
* gcc.dg/format/c99-sprintf-length-1.c: New test.
* gcc.dg/format/c99-sprintf-length-2.c: New test.
* gcc.dg/format/c99-sprintf-length-opt.c: New test.
gcc/ChangeLog:
2016-07-01 Martin Sebor <msebor@redhat.com>
PR middle-end/49905
* builtins.c (expand_builtin): Call maybe_emit_snprintf_trunc_warning.
Avoid issuing a duplicate warning already issued by -Wformat-length.
(maybe_emit_sprintf_chk_warning): Call maybe_emit_snprintf_trunc_warning.
(maybe_emit_snprintf_trunc_warning): New function.
* langhooks-def.h (lhd_check_format_length): Declare new function.
(LANG_HOOKS_CHECK_FORMAT_LENGTH): New macro.
* langhooks.c (lhd_warn_unused_global_decl): Define it.
* langhooks.h (struct lang_hooks_for_decls): Add check_format_length.
* doc/invoke.texi (-Wformat-length=): Document new option.
@@ -175,6 +175,7 @@ static rtx expand_builtin_memory_chk (tree, rtx, machine_mode,
enum built_in_function);
static void maybe_emit_chk_warning (tree, enum built_in_function);
static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
+static void maybe_emit_snprintf_trunc_warning (tree);
static void maybe_emit_free_warning (tree);
static tree fold_builtin_object_size (tree, tree);
@@ -6688,11 +6689,17 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_STPNCPY_CHK:
case BUILT_IN_STRCAT_CHK:
case BUILT_IN_STRNCAT_CHK:
+
case BUILT_IN_SNPRINTF_CHK:
case BUILT_IN_VSNPRINTF_CHK:
maybe_emit_chk_warning (exp, fcode);
break;
+ case BUILT_IN_SNPRINTF:
+ case BUILT_IN_VSNPRINTF:
+ maybe_emit_snprintf_trunc_warning (exp);
+ break;
+
case BUILT_IN_SPRINTF_CHK:
case BUILT_IN_VSPRINTF_CHK:
maybe_emit_sprintf_chk_warning (exp, fcode);
@@ -9346,6 +9353,12 @@ maybe_emit_chk_warning (tree exp, enum built_in_function fcode)
break;
case BUILT_IN_SNPRINTF_CHK:
case BUILT_IN_VSNPRINTF_CHK:
+ /* Diagnose output truncation with -Wformat-length. */
+ maybe_emit_snprintf_trunc_warning (exp);
+ /* -Wformat-length also diagnoses buffer size in excess of
+ the object size so avoid diagnosing it again below. */
+ if (warn_format_length)
+ return;
len = CALL_EXPR_ARG (exp, 1);
size = CALL_EXPR_ARG (exp, 3);
break;
@@ -9387,34 +9400,52 @@ maybe_emit_chk_warning (tree exp, enum built_in_function fcode)
exp, get_callee_fndecl (exp));
}
+/* Emit warning if output truncation is detected at compile time
+ in __snprintf/__vsnprintf calls. */
+
+static void
+maybe_emit_snprintf_trunc_warning (tree exp)
+{
+ /* Verify the required arguments in the original call. */
+ int nargs = call_expr_nargs (exp);
+
+ if (nargs < 3)
+ return;
+
+ tree fundecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
+ tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (fundecl));
+ tree *params = &CALL_EXPR_STATIC_CHAIN (exp) + 1;
+ lang_hooks.decls.check_format_length (fundecl, attrs, nargs, params);
+}
+
/* Emit warning if a buffer overflow is detected at compile time
in __sprintf_chk/__vsprintf_chk calls. */
static void
maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode)
{
- tree size, len, fmt;
- const char *fmt_str;
int nargs = call_expr_nargs (exp);
/* Verify the required arguments in the original call. */
if (nargs < 4)
return;
- size = CALL_EXPR_ARG (exp, 2);
- fmt = CALL_EXPR_ARG (exp, 3);
+ tree size = CALL_EXPR_ARG (exp, 2);
if (! tree_fits_uhwi_p (size) || integer_all_onesp (size))
return;
/* Check whether the format is a literal string constant. */
- fmt_str = c_getstr (fmt);
+ tree fmt = CALL_EXPR_ARG (exp, 3);
+ const char *fmt_str = c_getstr (fmt);
if (fmt_str == NULL)
return;
if (!init_target_chars ())
return;
+ tree len = NULL_TREE;
+
/* If the format doesn't contain % args or %%, we know its size. */
if (strchr (fmt_str, target_percent) == 0)
len = build_int_cstu (size_type_node, strlen (fmt_str));
@@ -9423,25 +9454,32 @@ maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode)
else if (fcode == BUILT_IN_SPRINTF_CHK
&& strcmp (fmt_str, target_percent_s) == 0)
{
- tree arg;
-
if (nargs < 5)
return;
- arg = CALL_EXPR_ARG (exp, 4);
+ tree arg = CALL_EXPR_ARG (exp, 4);
if (! POINTER_TYPE_P (TREE_TYPE (arg)))
return;
len = c_strlen (arg, 1);
- if (!len || ! tree_fits_uhwi_p (len))
- return;
}
- else
- return;
- if (! tree_int_cst_lt (len, size))
- warning_at (tree_nonartificial_location (exp),
- 0, "%Kcall to %D will always overflow destination buffer",
- exp, get_callee_fndecl (exp));
+ if (!len || !tree_fits_uhwi_p (len))
+ {
+ /* Check the whole format strings and ist arguments for possible
+ buffer overflow or truncation. */
+ tree fundecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
+ tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (fundecl));
+ tree *params = &CALL_EXPR_STATIC_CHAIN (exp) + 1;
+ lang_hooks.decls.check_format_length (fundecl, attrs, nargs, params);
+ return;
+ }
+ else if (!tree_int_cst_lt (len, size))
+ {
+ len = fold_build2 (PLUS_EXPR, size_type_node, len, integer_one_node);
+ warning_at (tree_nonartificial_location (exp),
+ 0, "%K %D writing %qE bytes into an object of size %qE",
+ exp, get_callee_fndecl (exp), len, size);
+ }
}
/* Emit warning if a free is called with address of a variable. */
@@ -9718,7 +9718,8 @@ handle_designated_init_attribute (tree *node, tree name, tree, int,
There are NARGS arguments in the array ARGARRAY. LOC should be used for
diagnostics. */
void
-check_function_arguments (location_t loc, const_tree fntype, int nargs,
+check_function_arguments (location_t loc, const_tree fndecl,
+ const_tree fntype, int nargs,
tree *argarray)
{
/* Check for null being passed in a pointer argument that must be
@@ -9730,7 +9731,7 @@ check_function_arguments (location_t loc, const_tree fntype, int nargs,
/* Check for errors in format strings. */
if (warn_format || warn_suggest_attribute_format)
- check_function_format (TYPE_ATTRIBUTES (fntype), nargs, argarray);
+ check_function_format (fndecl, TYPE_ATTRIBUTES (fntype), nargs, argarray);
if (warn_format)
check_function_sentinel (fntype, nargs, argarray);
@@ -782,7 +782,8 @@ extern const char *fname_as_string (int);
extern tree fname_decl (location_t, unsigned, tree);
extern int check_user_alignment (const_tree, bool);
-extern void check_function_arguments (location_t loc, const_tree, int, tree *);
+extern void check_function_arguments (location_t loc, const_tree, const_tree,
+ int, tree *);
extern void check_function_arguments_recurse (void (*)
(void *, tree,
unsigned HOST_WIDE_INT),
@@ -790,7 +791,7 @@ extern void check_function_arguments_recurse (void (*)
unsigned HOST_WIDE_INT);
extern bool check_builtin_function_arguments (location_t, vec<location_t>,
tree, int, tree *);
-extern void check_function_format (tree, int, tree *);
+extern void check_function_format (const_tree, const_tree, int, tree *);
extern tree handle_unused_attribute (tree *, tree, tree, int, bool *);
extern tree handle_format_attribute (tree *, tree, tree, int, bool *);
extern tree handle_format_arg_attribute (tree *, tree, tree, int, bool *);
@@ -29,6 +29,12 @@ along with GCC; see the file COPYING3. If not see
#include "intl.h"
#include "langhooks.h"
#include "c-format.h"
+#include "builtins.h"
+
+#include "backend.h"
+#include "gimple.h"
+#include "ssa.h"
+#include "stor-layout.h"
/* Handle attributes associated with format checking. */
@@ -44,9 +50,16 @@ enum format_type { printf_format_type, asm_fprintf_format_type,
struct function_format_info
{
+ built_in_function fncode;
int format_type; /* type of format (printf, scanf, etc.) */
unsigned HOST_WIDE_INT format_num; /* number of format argument */
unsigned HOST_WIDE_INT first_arg_num; /* number of first arg (zero for varargs) */
+ /* The destination object size or -1 if unknown. */
+ unsigned HOST_WIDE_INT objsize;
+
+ /* True for functions whose output is bounded by a size argument
+ (e.g., snprintf and vsnprintf). */
+ bool bounded;
};
static bool decode_format_attr (tree, function_format_info *, int);
@@ -65,6 +78,16 @@ static int first_target_format_type;
static const char *format_name (int format_num);
static int format_flags (int format_num);
+struct format_check_results;
+
+static void
+check_format_length (location_t,
+ format_check_results *,
+ const function_format_info *,
+ const format_char_info *,
+ const char *, size_t, size_t,
+ const conversion_spec *, tree);
+
/* Given a string S of length LINE_WIDTH, find the visual column
corresponding to OFFSET bytes. */
@@ -378,6 +401,542 @@ decode_format_attr (tree args, function_format_info *info, int validated_p)
return true;
}
+
+/* Description of a conversion specification. */
+struct conversion_spec
+{
+ /* A bitmap of flags, one for each character. */
+ int flags [256 / sizeof (int)];
+ int width; /* Numeric width. */
+ int precision; /* Numeric precision. */
+
+ tree star_width; /* Width specified via the '*' character. */
+ tree star_precision; /* Precision specified via the asterisk. */
+
+ format_lengths modifier; /* Length modifier. */
+ char specifier; /* Format specifier character. */
+
+ unsigned have_width: 1; /* Numeric width was given. */
+ unsigned have_precision: 1; /* Numeric precision was given. */
+
+ /* Return True when a the format flag CHR has been used. */
+ bool get_flag (char chr) const
+ {
+ unsigned char c = chr & 0xff;
+ return flags [c / (CHAR_BIT * sizeof *flags)]
+ & (1 << (c % (CHAR_BIT * sizeof *flags)));
+ }
+
+ /* Make a record of the format flag CHR having been used. */
+ void set_flag (char chr)
+ {
+ unsigned char c = chr & 0xff;
+ flags [c / (CHAR_BIT * sizeof *flags)]
+ |= (1 << (c % (CHAR_BIT * sizeof *flags)));
+ }
+};
+
+/* Return the logarithm of X in BASE. */
+
+static int
+ilog (unsigned HOST_WIDE_INT x, int base)
+{
+ int res = 0;
+ do {
+ ++res;
+ x /= base;
+ } while (x);
+ return res;
+}
+
+/* Return the logarithm of tree node X in BASE, incremented by 1 when
+ the optional PLUS sign is True, plus the length of the octal ('0')
+ or hexadecimal ('0x') prefix when PREFIX is True. Return -1 when
+ 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;
+}
+
+/* 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. */
+
+static format_char_info::fmtresult
+format_integer (const conversion_spec *spec, tree arg)
+{
+ /* Set WIDTH and PRECISION to either the values in the format
+ specification or to zero. */
+ int width = spec->have_width ? spec->width : 0;
+ int prec = spec->have_precision ? spec->precision : 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;
+
+ /* The minimum and maximum number of bytes produced by the directive. */
+ format_char_info::fmtresult res;
+
+ bool sign = spec->specifier == 'd' || spec->specifier == 'i';
+ tree type = NULL_TREE;
+
+ switch (spec->modifier)
+ {
+ case FMT_LEN_none:
+ type = sign ? integer_type_node : unsigned_type_node;
+ break;
+
+ case FMT_LEN_h:
+ type = sign ? short_integer_type_node : short_unsigned_type_node;
+ break;
+
+ case FMT_LEN_hh:
+ type = sign ? signed_char_type_node : unsigned_char_type_node;
+ break;
+
+ case FMT_LEN_l:
+ type = sign ? long_integer_type_node : long_unsigned_type_node;
+ break;
+
+ case FMT_LEN_ll:
+ type = sign ? long_integer_type_node : long_unsigned_type_node;
+ break;
+
+ case FMT_LEN_z:
+ type = sign ? ptrdiff_type_node : size_type_node;
+ break;
+
+ case FMT_LEN_t:
+ type = sign ? ptrdiff_type_node : size_type_node;
+ break;
+
+ case FMT_LEN_j:
+ type = sign ? ptrdiff_type_node : size_type_node;
+ break;
+
+ case FMT_LEN_H:
+ case FMT_LEN_D:
+ case FMT_LEN_DD:
+ case FMT_LEN_MAX:
+ /* FIXME: Implement this. */
+ res.min = res.max = -1;
+ return res;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ /* A 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;
+
+ /* The argument is likely unbounded (i.e., the range of its values
+ beyond the limits of its type is likely unknown. */
+ res.bounded = false;
+
+ 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 = type;
+ }
+ else if (TREE_CODE (arg) == INTEGER_CST)
+ res.bounded = true;
+ else
+ {
+ /* 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);
+ }
+
+ if (argtype)
+ {
+ tree argmin = NULL_TREE;
+ tree argmax = NULL_TREE;
+
+ if (arg && TREE_CODE (arg) == SSA_NAME)
+ {
+ /* Try to determine the range of values of the argument. */
+ wide_int min, max;
+ enum value_range_type range_type = get_range_info (arg, &min, &max);
+ if (range_type == VR_RANGE)
+ {
+ 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 ());
+
+ /* The argument is bounded by the range of values
+ determined by VRP. */
+ res.bounded = true;
+ }
+ else if (range_type == VR_ANTI_RANGE)
+ {
+ /* Handle anti-ranges if/when bug 71690 is ever resolved. */
+ }
+ }
+
+ 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 (type);
+ int argprec = TYPE_PRECISION (argtype);
+
+ if (argprec < typeprec)
+ {
+ 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, type, integer_one_node,
+ build_int_cst (integer_type_node,
+ typeprec - 1));
+ }
+ }
+
+ /* Recursively compute the minimum and maximum from the known range,
+ taking care to swap them if the lower bound results in longer
+ output than the upper bound (e.g., in the range [-1, 0]. */
+ res.min = format_integer (spec, argmin).min;
+ res.max = format_integer (spec, argmax).max;
+ if (res.max < res.min)
+ {
+ int tmp = res.max;
+ res.max = res.min;
+ res.min = tmp;
+ }
+ return res;
+ }
+
+ /* Base to format the number in. */
+ int base = 10;
+ /* True when a signed conversion is preceded by a sign or space. */
+ bool maybesign = false;
+
+ switch (spec->specifier)
+ {
+ case 'd':
+ case 'i':
+ /* Space is only effective for signed conversions. */
+ maybesign = spec->get_flag (' ');
+ case 'u':
+ break;
+ case 'o':
+ base = 8;
+ break;
+ case 'X':
+ case 'x':
+ base = 16;
+ break;
+ default:
+ gcc_unreachable ();
+ }
+
+ /* Convert the argument to the type of the directive. */
+ arg = fold_convert (type, arg);
+
+ maybesign |= spec->get_flag ('+');
+ int len = tree_digits (arg, base, maybesign, spec->get_flag ('#'));
+
+ if (len < prec)
+ len = prec;
+
+ if (len < width)
+ len = width;
+
+ res.max = len;
+ res.min = res.max;
+ res.bounded = true;
+
+ 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 format_char_info::fmtresult
+format_floating (const conversion_spec *spec, tree arg)
+{
+ /* Set WIDTH and PRECISION to either the values in the format
+ specification or to zero. */
+ int width = spec->have_width ? spec->width : 0;
+ int prec = spec->have_precision ? spec->precision : -1;
+
+ if (spec->star_width)
+ width = (TREE_CODE (spec->star_width) == INTEGER_CST)
+ ? tree_to_shwi (spec->star_width) : 0;
+
+ if (spec->star_precision)
+ prec = (TREE_CODE (spec->star_precision) == INTEGER_CST)
+ ? tree_to_shwi (spec->star_precision) : 0;
+
+ tree type = arg ? TREE_TYPE (arg) : NULL_TREE;
+
+ switch (spec->modifier)
+ {
+ case FMT_LEN_none:
+ if (!type)
+ type = double_type_node;
+ break;
+
+ case FMT_LEN_L:
+ if (!type)
+ type = long_double_type_node;
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ /* The minimum and maximum number of bytes produced by the directive. */
+ format_char_info::fmtresult res;
+
+ int expdigs = -1; /* Number of exponent digits or -1 when unknown. */
+ int negative = -1; /* 1 when arg < 0, 0 when arg >= 0, -1 when unknown. */
+
+ if (arg && TREE_CODE (arg) == REAL_CST)
+ {
+ expdigs = ilog (real_exponent (TREE_REAL_CST_PTR (arg)), 10);
+ negative = real_isneg (TREE_REAL_CST_PTR (arg));
+ }
+ else if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
+ {
+ /* Compute T_MAX_EXP for base 2. */
+ const double log10_2 = .30102999566398119521;
+ expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;
+ }
+
+ int logexpdigs = ilog (expdigs, 10);
+
+ switch (spec->specifier)
+ {
+ case 'A':
+ case 'a':
+ /* The minimum output is "0x.p+0". */
+ res.min = 6 + (0 < prec ? prec : 0);
+ /* FIXME: Figure out the maximum. */
+ res.max = -1;
+ if (res.min < width)
+ res.min = width;
+ break;
+
+ case 'E':
+ case 'e':
+ /* The minimum output is "[-+]1.234567e+00" for an IEEE double
+ regardless of the value of the actual argument. */
+ res.min = (0 < negative || spec->get_flag ('+') || spec->get_flag (' '))
+ + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+ + 2 /* e+ */ + (logexpdigs < 2 ? 2 : logexpdigs);
+ /* The maximum output is "-1.234567e+123" for a double and one more
+ byte for a large exponent for a long louble. */
+ res.max = negative < 0 ? res.min + 2 + (spec->get_flag ('L')) : res.min;
+ if (res.min < width)
+ res.min = width;
+ if (res.max < width)
+ res.max = width;
+ break;
+
+ case 'F':
+ case 'f':
+ /* The minimum output is "1.234567" regardless of the value
+ of the actual argument. */
+ res.min = 2 + (prec < 0 ? 6 : prec);
+ /* The maximum depends on the magnitude of the value but it's
+ at most 316 bytes for double and 4940 for long double, plus
+ precision if non-negative, or 6. */
+ /* res.max = (spec->get_flag ('L') ? 4934 : 310) */
+ /* + (prec < 0 ? 6 : prec ? prec + 1 : 0); */
+ res.max = expdigs + (prec < 0 ? 6 : prec ? prec + 1 : 0);
+ break;
+
+ case 'G':
+ case 'g':
+ /* Treat this the same as '%F' for now even though that's
+ inaccurate. */
+ res.min = 2 + (prec < 0 ? 6 : prec);
+ res.max = (spec->get_flag ('L') ? 4934 : 310)
+ + (prec < 0 ? 6 : prec ? prec + 1 : 0);
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ /* The argument is only considered bounded when the range of output
+ bytes is exact. */
+ res.bounded = res.min == res.max;
+ return res;
+}
+
+/* Return the minimum and maximum number of characters formatted
+ by the '%c' and '%s' format directives and ther wide character
+ forms. */
+
+static format_char_info::fmtresult
+format_string (const conversion_spec *spec, tree arg)
+{
+ int width = spec->have_width ? spec->width : 0;
+ int prec = spec->have_precision ? spec->precision : -1;
+
+ if (spec->star_width)
+ width = (TREE_CODE (spec->star_width) == INTEGER_CST)
+ ? tree_to_shwi (spec->star_width) : 0;
+
+ if (spec->star_precision)
+ prec = (TREE_CODE (spec->star_precision) == INTEGER_CST)
+ ? tree_to_shwi (spec->star_precision) : -1;
+
+ format_char_info::fmtresult res;
+
+ /* The argument is likely unbounded (i.e., its length is likely
+ unknown. */
+ res.bounded = false;
+
+ /* The number of bytes formatted. This applies to both '%s' and
+ '%ls' where precision and width are in converted characters
+ (i.e., bytes). */
+ int nbytes;
+
+ if (spec->specifier == 'c')
+ {
+ if (spec->modifier == FMT_LEN_l)
+ {
+ int nul = arg && TREE_CODE (arg) == INTEGER_CST
+ ? integer_zerop (arg) : -1;
+
+ /* A '%lc' directive is the same as '%ls' for a two element
+ wide string character with the second element of NUL, so
+ when the character is unknown the minimum number of bytes
+ is the smaller of either 0 (at level 1) or 1 (at level 2)
+ and WIDTH, and the maximum is MB_CUR_MAX in the selected
+ locale, which is unfortunately, unknown. */
+ res.min = 0 < width ? width : 1 < warn_format_length ? nul < 1: !nul;
+ res.max = -1;
+ return res;
+ }
+
+ /* A plain '%c' directive. */
+ nbytes = 1;
+ }
+ else if (tree slen = arg ? c_strlen (arg, 1) : NULL_TREE)
+ {
+ /* A '%s' directive with a constant string. */
+ nbytes = tree_to_shwi (slen);
+ if (0 <= prec && prec < nbytes)
+ nbytes = prec;
+
+ if (spec->modifier == FMT_LEN_l)
+ {
+ /* For a '%ls' directive the minimum number of bytes is
+ the greater of WIDTH and the string length, and the
+ maximum is either PRECISION when specified or
+ MB_CUR_MAX * length, which is unknown, so set it
+ to -1. */
+ res.min = nbytes < width ? width : nbytes;
+ /* FIXME: Be smarter about computing the maximum. Scan
+ the wide string for any 8-bit characters and if it
+ contains none, use its length for the maximum. */
+ res.max = 0 <= prec ? prec : -1;
+
+ res.bounded = -1 < res.max;
+ return res;
+ }
+ }
+ 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. */
+ res.min = 0 < width ? width : 1 < warn_format_length && prec ? 1 : 0;
+ res.max = 0 <= prec ? prec : -1;
+ res.bounded = res.min == res.max;
+ return res;
+ }
+
+ if (nbytes < width)
+ nbytes = width;
+
+ res.min = res.max = nbytes;
+
+ /* The length is exact. */
+ res.bounded = true;
+
+ return res;
+}
/* Check a call to a format function against a parameter list. */
@@ -670,21 +1229,27 @@ static const format_flag_pair strfmon_flag_pairs[] =
};
+/* FIXME: Suppress the warning to avoid having to change all
+ the format_char_info arrays below and to reduce the footprint
+ of the patch until it's been reviewed and accepted. */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+
static const format_char_info print_char_table[] =
{
/* C89 conversion specifiers. */
- { "di", 0, STD_C89, { T89_I, T99_SC, T89_S, T89_L, T9L_LL, TEX_LL, T99_SST, T99_PD, T99_IM, BADLEN, BADLEN, BADLEN }, "-wp0 +'I", "i", NULL },
- { "oxX", 0, STD_C89, { T89_UI, T99_UC, T89_US, T89_UL, T9L_ULL, TEX_ULL, T99_ST, T99_UPD, T99_UIM, BADLEN, BADLEN, BADLEN }, "-wp0#", "i", NULL },
- { "u", 0, STD_C89, { T89_UI, T99_UC, T89_US, T89_UL, T9L_ULL, TEX_ULL, T99_ST, T99_UPD, T99_UIM, BADLEN, BADLEN, BADLEN }, "-wp0'I", "i", NULL },
- { "fgG", 0, STD_C89, { T89_D, BADLEN, BADLEN, T99_D, BADLEN, T89_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "", NULL },
- { "eE", 0, STD_C89, { T89_D, BADLEN, BADLEN, T99_D, BADLEN, T89_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#I", "", NULL },
- { "c", 0, STD_C89, { T89_I, BADLEN, BADLEN, T94_WI, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-w", "", NULL },
- { "s", 1, STD_C89, { T89_C, BADLEN, BADLEN, T94_W, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "cR", NULL },
+ { "di", 0, STD_C89, { T89_I, T99_SC, T89_S, T89_L, T9L_LL, TEX_LL, T99_SST, T99_PD, T99_IM, BADLEN, BADLEN, BADLEN }, "-wp0 +'I", "i", NULL, format_integer },
+ { "oxX", 0, STD_C89, { T89_UI, T99_UC, T89_US, T89_UL, T9L_ULL, TEX_ULL, T99_ST, T99_UPD, T99_UIM, BADLEN, BADLEN, BADLEN }, "-wp0#", "i", NULL, format_integer },
+ { "u", 0, STD_C89, { T89_UI, T99_UC, T89_US, T89_UL, T9L_ULL, TEX_ULL, T99_ST, T99_UPD, T99_UIM, BADLEN, BADLEN, BADLEN }, "-wp0'I", "i", NULL, format_integer},
+ { "fgG", 0, STD_C89, { T89_D, BADLEN, BADLEN, T99_D, BADLEN, T89_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "", NULL, format_floating },
+ { "eE", 0, STD_C89, { T89_D, BADLEN, BADLEN, T99_D, BADLEN, T89_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#I", "", NULL, format_floating },
+ { "c", 0, STD_C89, { T89_I, BADLEN, BADLEN, T94_WI, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-w", "", NULL, format_string },
+ { "s", 1, STD_C89, { T89_C, BADLEN, BADLEN, T94_W, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "cR", NULL, format_string },
{ "p", 1, STD_C89, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-w", "c", NULL },
{ "n", 1, STD_C89, { T89_I, T99_SC, T89_S, T89_L, T9L_LL, BADLEN, T99_SST, T99_PD, T99_IM, BADLEN, BADLEN, BADLEN }, "", "W", NULL },
/* C99 conversion specifiers. */
- { "F", 0, STD_C99, { T99_D, BADLEN, BADLEN, T99_D, BADLEN, T99_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "", NULL },
- { "aA", 0, STD_C99, { T99_D, BADLEN, BADLEN, T99_D, BADLEN, T99_LD, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp0 +#", "", NULL },
+ { "F", 0, STD_C99, { T99_D, BADLEN, BADLEN, T99_D, BADLEN, T99_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "", NULL, format_floating },
+ { "aA", 0, STD_C99, { T99_D, BADLEN, BADLEN, T99_D, BADLEN, T99_LD, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp0 +#", "", NULL, format_floating },
/* X/Open conversion specifiers. */
{ "C", 0, STD_EXT, { TEX_WI, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-w", "", NULL },
{ "S", 1, STD_EXT, { TEX_W, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "R", NULL },
@@ -880,6 +1445,8 @@ static const format_char_info monetary_char_table[] =
{ NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL }
};
+#pragma GCC diagnostic pop
+
/* This must be in the same order as enum format_type. */
static const format_kind_info format_types_orig[] =
{
@@ -984,8 +1551,35 @@ struct format_check_results
int number_unterminated;
/* Number of leaves of the format argument that were not counted above. */
int number_other;
+ /* Number of characters written by the formatting function, exact,
+ minimum and maximum when an exact number cannot be determined.
+ Setting the minimum to a negative value disables all length
+ tracking for the remainder of the format string.
+ Setting either of the other two members disables the exact or
+ maximum length tracking, respectively, but continues to track
+ the maximum. */
+ int number_chars;
+ int number_chars_min;
+ int number_chars_max;
+
+ /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX
+ is the output of all directives is determined to be bounded to some
+ subrange of their types or possible lengths, false otherwise. */
+ bool bounded;
+
/* Location of the format string. */
location_t format_string_loc;
+
+ /* Increment the number of output characters by N. */
+ void inc_number_chars (int n = 1) {
+ gcc_assert (0 <= n);
+ if (0 <= number_chars)
+ number_chars += n;
+ if (0 <= number_chars_min)
+ number_chars_min += n;
+ if (0 <= number_chars_max)
+ number_chars_max += n;
+ }
};
struct format_check_context
@@ -1015,7 +1609,7 @@ format_flags (int format_num)
gcc_unreachable ();
}
-static void check_format_info (function_format_info *, tree);
+static void check_format_info (function_format_info *, const_tree, tree);
static void check_format_arg (void *, tree, unsigned HOST_WIDE_INT);
static void check_format_info_main (format_check_results *,
function_format_info *,
@@ -1069,17 +1663,15 @@ decode_format_type (const char *s)
attribute themselves. */
void
-check_function_format (tree attrs, int nargs, tree *argarray)
+check_function_format (const_tree fndecl, const_tree attrs, int nargs, tree *argarray)
{
- tree a;
-
/* See if this function has any format attributes. */
- for (a = attrs; a; a = TREE_CHAIN (a))
+ for (const_tree a = attrs; a; a = TREE_CHAIN (a))
{
if (is_attribute_p ("format", TREE_PURPOSE (a)))
{
/* Yup; check it. */
- function_format_info info;
+ function_format_info info = function_format_info ();
decode_format_attr (TREE_VALUE (a), &info, /*validated=*/true);
if (warn_format)
{
@@ -1090,7 +1682,7 @@ check_function_format (tree attrs, int nargs, tree *argarray)
int i;
for (i = nargs - 1; i >= 0; i--)
params = tree_cons (NULL_TREE, argarray[i], params);
- check_format_info (&info, params);
+ check_format_info (&info, fndecl, params);
}
if (warn_suggest_attribute_format && info.first_arg_num == 0
&& (format_types[info.format_type].flags
@@ -1378,26 +1970,155 @@ get_flag_spec (const format_flag_spec *spec, int flag, const char *predicates)
return NULL;
}
-
/* Check the argument list of a call to printf, scanf, etc.
INFO points to the function_format_info structure.
PARAMS is the list of argument values. */
static void
-check_format_info (function_format_info *info, tree params)
+check_format_info (function_format_info *info, const_tree fndecl, tree params)
{
+ /* Avoid issuing one set of diagnostics during parsing, and another
+ (possibly duplicate) set when expanding printf built-ins. There
+ are tradeoffs between the diagnostics issued during parsing and
+ later: the former doesn't benefit from constant propagation or
+ range information from the VRP pass, and the latter may not have
+ the original types of all the arguments. As a result, for
+ arguments of narrower types with no range information that are
+ promoted to wider types the diagnostics during parsing can reduce
+ the rate of false positives by assuming the range of values of
+ the original type as opposed to the one after promotion.
+ */
+ const struct restore_on_return
+ {
+ int warn_format_save;
+ int warn_format_length_save;
+
+ restore_on_return ()
+ : warn_format_save (warn_format),
+ warn_format_length_save (warn_format_length)
+ {
+ if (!optimize ^ !currently_expanding_gimple_stmt)
+ warn_format_length = 0;
+ if (currently_expanding_gimple_stmt)
+ warn_format = 0;
+ }
+
+ ~restore_on_return () {
+ warn_format = warn_format_save;
+ warn_format_length = warn_format_length_save;
+ }
+ } save_warn_format_settings;
+
format_check_context format_ctx;
unsigned HOST_WIDE_INT arg_num;
tree format_tree;
format_check_results res;
+
+ /* Buffer size argument number (snprintf and vsnprintf). */
+ unsigned idx_size = -1;
+
+ /* Object size argument number (snprintf_chk and vsnprintf_chk). */
+ unsigned idx_objsize = -1;
+
+ if (fndecl && DECL_BUILT_IN (fndecl)
+ && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL)
+ {
+ /* Save the function code to use in diagnostics. */
+ info->fncode = DECL_FUNCTION_CODE (fndecl);
+
+ switch (DECL_FUNCTION_CODE (fndecl))
+ {
+ case BUILT_IN_SNPRINTF:
+ idx_size = 2;
+ info->bounded = true;
+ break;
+
+ case BUILT_IN_SNPRINTF_CHK:
+ idx_size = 2;
+ idx_objsize = 4;
+ info->bounded = true;
+ break;
+
+ case BUILT_IN_SPRINTF_CHK:
+ idx_objsize = 3;
+ break;
+
+ case BUILT_IN_VSNPRINTF:
+ idx_size = 2;
+ info->bounded = true;
+ break;
+
+ case BUILT_IN_VSNPRINTF_CHK:
+ idx_size = 2;
+ idx_objsize = 4;
+ info->bounded = true;
+ break;
+
+ case BUILT_IN_VSPRINTF_CHK:
+ idx_objsize = 3;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* The size of the destination as in snprintf(dest, size, ...). */
+ unsigned HOST_WIDE_INT dstsize = ~(unsigned HOST_WIDE_INT)0;
+
+ /* The size of the destination determined by __builtin_object_size. */
+ unsigned HOST_WIDE_INT objsize = ~(unsigned HOST_WIDE_INT)0;
+
/* Skip to format argument. If the argument isn't available, there's
no work for us to do; prototype checking will catch the problem. */
for (arg_num = 1; ; ++arg_num)
{
if (params == 0)
return;
+
+ /* Assume the format string argument never appears before the object
+ size argument in any of the checked signatures. */
if (arg_num == info->format_num)
- break;
+ break;
+
+ if (arg_num == idx_size)
+ {
+ tree size = TREE_VALUE (params);
+ if (TREE_CODE (size) == INTEGER_CST)
+ {
+ dstsize = tree_to_uhwi (size);
+ if (dstsize > ~(unsigned HOST_WIDE_INT)0 / 2)
+ warning_at (EXPR_LOC_OR_LOC (params, input_location),
+ 0, "specified destination size %qwu 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 (arg_num == idx_objsize)
+ {
+ /* This is in all likelihood the result of __builtin_object_size
+ which should be constant but there's a small change that it
+ isn't. */
+ tree size = TREE_VALUE (params);
+ if (TREE_CODE (size) == INTEGER_CST)
+ objsize = tree_to_uhwi (size);
+ }
+
params = TREE_CHAIN (params);
}
format_tree = TREE_VALUE (params);
@@ -1405,6 +2126,28 @@ check_format_info (function_format_info *info, tree params)
if (format_tree == 0)
return;
+ if (info->bounded && !dstsize)
+ {
+ /* As a special case, bounded function allow the explicitly
+ specified destination size argument to be zero as a request
+ to determine the number of bytes on output without actually
+ writing any output. Disable length checking for those. */
+ info->objsize = ~(unsigned HOST_WIDE_INT)0;
+ }
+ 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 (dstsize != ~(unsigned HOST_WIDE_INT)0 && objsize < dstsize)
+ {
+ warning (0, "specified destination size %qwu exceeds "
+ "the size of the object %qwu", dstsize, objsize);
+
+ }
+ }
+
res.number_non_literal = 0;
res.number_extra_args = 0;
res.extra_arg_loc = UNKNOWN_LOCATION;
@@ -1414,6 +2157,10 @@ check_format_info (function_format_info *info, tree params)
res.number_unterminated = 0;
res.number_other = 0;
res.format_string_loc = input_location;
+ res.number_chars = 0;
+ res.number_chars_min = 0;
+ res.number_chars_max = 0;
+ res.bounded = true;
format_ctx.res = &res;
format_ctx.info = info;
@@ -1688,6 +2435,42 @@ check_format_arg (void *ctx, tree format_tree,
params, arg_num, fwt_pool);
}
+/* Given the formatting result described by RES, return the number
+ of bytes remaining in the destination buffer whose size is OBJSIZE. */
+
+static inline size_t
+bytes_remaining (const function_format_info *info,
+ const format_check_results *res)
+{
+ size_t navail = info->objsize;
+
+ if (1 < warn_format_length || res->bounded)
+ {
+ /* At level 2, or when all directives output an exact number
+ of bytes or when their arguments were bounded by known
+ ranges, use the greater of the two byte counters if it's
+ valid to compute the result. */
+ if (0 <= res->number_chars_max)
+ navail -= res->number_chars_max;
+ else if (0 <= res->number_chars)
+ navail -= res->number_chars;
+ else if (0 <= res->number_chars_min)
+ navail -= res->number_chars_min;
+ }
+ else
+ {
+ /* At level 1 use the smaller of the byte counters to compute
+ the result. */
+ if (0 <= res->number_chars)
+ navail -= res->number_chars;
+ else if (0 <= res->number_chars_min)
+ navail -= res->number_chars_min;
+ else if (0 <= res->number_chars_max)
+ navail -= res->number_chars_max;
+ }
+
+ return navail;
+}
/* Do the main part of checking a call to a format function. FORMAT_CHARS
is the NUL-terminated format string (which at this point may contain
@@ -1725,7 +2508,7 @@ check_format_info_main (format_check_results *res,
enum format_lengths length_chars_val = FMT_LEN_none;
enum format_std_version length_chars_std = STD_C89;
int format_char;
- tree cur_param;
+ tree cur_param = NULL_TREE;
tree wanted_type;
int main_arg_num = 0;
tree main_arg_params = 0;
@@ -1738,13 +2521,53 @@ check_format_info_main (format_check_results *res,
format_wanted_type *last_wanted_type = NULL;
const format_length_info *fli = NULL;
const format_char_info *fci = NULL;
+ conversion_spec cvtspec = conversion_spec ();
char flag_chars[256];
int alloc_flag = 0;
int scalar_identity_flag = 0;
const char *format_start;
if (*format_chars++ != '%')
- continue;
+ {
+ /* There must always be at least one byte of space in the buffer
+ for the terminating NUL that's appended after the format string
+ has been processed. */
+ size_t navail = bytes_remaining (info, res);
+ /* The destination will have already overflowed if the number of
+ bytes has wrapped around zero. */
+ bool overflowed = SIZE_MAX / 2 <= navail;
+ if (!overflowed && navail < 1)
+ {
+ unsigned long off
+ = (unsigned long)(format_chars - orig_format_chars);
+
+ location_t loc
+ = location_from_offset (format_string_loc, off);
+
+ /* Differentiate between an exact and inexact buffer overflow
+ or truncation. */
+ const char *fmtstr;
+ if (res->number_chars < 0)
+ fmtstr = info->bounded
+ ? "output may be truncated at or before format character "
+ "%qc at offset %qlu past the end of a region of size %qlu"
+ : "writing format character %qc at offset %qlu "
+ "in a region of size %qlu";
+ else
+ fmtstr = info->bounded
+ ? "output truncated at format character %qc at offset %qlu "
+ "just past the end of a region of size %qlu"
+ : "writing format character %qc at offset %qlu "
+ "just past the end of a region of size %qlu";
+ warning_at (loc, OPT_Wformat_length_, fmtstr,
+ format_chars [-1], off - 1,
+ (unsigned long)info->objsize);
+ }
+
+ res->inc_number_chars ();
+ continue;
+ }
+
if (*format_chars == 0)
{
warning_at (location_from_offset (format_string_loc,
@@ -1755,9 +2578,16 @@ check_format_info_main (format_check_results *res,
}
if (*format_chars == '%')
{
+ res->inc_number_chars ();
++format_chars;
continue;
}
+
+ /* Save the place of the first character of the format conversion
+ specification so that the full directive can be printed in
+ diagnostics. */
+ const char *cvtbeg = format_chars;
+
flag_chars[0] = 0;
if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0)
@@ -1805,6 +2635,8 @@ check_format_info_main (format_check_results *res,
i = strlen (flag_chars);
flag_chars[i++] = *format_chars;
flag_chars[i] = 0;
+
+ cvtspec.set_flag (*format_chars);
}
if (s->skip_next_char)
{
@@ -1859,6 +2691,7 @@ check_format_info_main (format_check_results *res,
else
{
cur_param = TREE_VALUE (params);
+ cvtspec.star_width = cur_param;
if (has_operand_number <= 0)
{
params = TREE_CHAIN (params);
@@ -1892,23 +2725,25 @@ check_format_info_main (format_check_results *res,
/* Possibly read a numeric width. If the width is zero,
we complain if appropriate. */
int non_zero_width_char = FALSE;
- int found_width = FALSE;
+ cvtspec.have_width = FALSE;
+ const char *widthbeg = format_chars;
while (ISDIGIT (*format_chars))
{
- found_width = TRUE;
+ cvtspec.have_width = TRUE;
if (*format_chars != '0')
non_zero_width_char = TRUE;
++format_chars;
}
- if (found_width && !non_zero_width_char &&
+ if (cvtspec.have_width && !non_zero_width_char &&
(fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD))
warning_at (format_string_loc, OPT_Wformat_,
"zero width in %s format", fki->name);
- if (found_width)
+ if (cvtspec.have_width)
{
i = strlen (flag_chars);
flag_chars[i++] = fki->width_char;
flag_chars[i] = 0;
+ cvtspec.width = strtol (widthbeg, 0, 10);
}
}
}
@@ -1925,8 +2760,12 @@ check_format_info_main (format_check_results *res,
format_chars - orig_format_chars),
OPT_Wformat_,
"empty left precision in %s format", fki->name);
+
+ const char *precbeg = format_chars;
while (ISDIGIT (*format_chars))
++format_chars;
+ cvtspec.precision = strtol (precbeg, 0, 10);
+ cvtspec.have_precision = 1;
}
/* Read any format precision, possibly * or *m$. */
@@ -1970,6 +2809,7 @@ check_format_info_main (format_check_results *res,
else
{
cur_param = TREE_VALUE (params);
+ cvtspec.star_precision = cur_param;
if (has_operand_number <= 0)
{
params = TREE_CHAIN (params);
@@ -2006,8 +2846,15 @@ check_format_info_main (format_check_results *res,
format_chars - orig_format_chars),
OPT_Wformat_,
"empty precision in %s format", fki->name);
+
+ const char *precbeg = format_chars;
while (ISDIGIT (*format_chars))
++format_chars;
+ if (format_chars - precbeg)
+ {
+ cvtspec.precision = strtol (precbeg, 0, 10);
+ cvtspec.have_precision = TRUE;
+ }
}
}
@@ -2047,7 +2894,7 @@ check_format_info_main (format_check_results *res,
{
while (fli->name != 0
&& strncmp (fli->name, format_chars, strlen (fli->name)))
- fli++;
+ fli++;
if (fli->name != 0)
{
format_chars += strlen (fli->name);
@@ -2057,6 +2904,7 @@ check_format_info_main (format_check_results *res,
length_chars = fli->double_name;
length_chars_val = fli->double_index;
length_chars_std = fli->double_std;
+ cvtspec.modifier = fli->double_index;
}
else
{
@@ -2064,6 +2912,7 @@ check_format_info_main (format_check_results *res,
length_chars_val = fli->index;
length_chars_std = fli->std;
scalar_identity_flag = fli->scalar_identity_flag;
+ cvtspec.modifier = fli->index;
}
i = strlen (flag_chars);
flag_chars[i++] = fki->length_code_char;
@@ -2106,6 +2955,7 @@ check_format_info_main (format_check_results *res,
}
}
+ /* Read the conversion specifier character. */
format_char = *format_chars;
if (format_char == 0
|| (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
@@ -2117,6 +2967,7 @@ check_format_info_main (format_check_results *res,
"conversion lacks type at end of format");
continue;
}
+
format_chars++;
fci = fki->conversion_specs;
while (fci->format_chars != 0
@@ -2148,6 +2999,8 @@ check_format_info_main (format_check_results *res,
C_STD_NAME (fci->std), format_char, fki->name);
}
+ cvtspec.specifier = format_char;
+
/* Validate the individual flags used, removing any that are invalid. */
{
int d = 0;
@@ -2327,7 +3180,14 @@ check_format_info_main (format_check_results *res,
/* Finally. . .check type of argument against desired type! */
if (info->first_arg_num == 0)
- continue;
+ {
+ if (warn_format_length)
+ check_format_length (format_string_loc, res, info, fci,
+ cvtbeg, format_chars - cvtbeg,
+ cvtbeg - orig_format_chars,
+ &cvtspec, NULL_TREE);
+ continue;
+ }
if ((fci->pointer_count == 0 && wanted_type == void_type_node)
|| suppressed)
{
@@ -2366,7 +3226,7 @@ check_format_info_main (format_check_results *res,
}
wanted_type_ptr = &main_wanted_type;
- while (fci)
+ for (const format_char_info *pfci = fci; pfci; )
{
if (params == 0)
cur_param = NULL;
@@ -2378,9 +3238,9 @@ check_format_info_main (format_check_results *res,
wanted_type_ptr->wanted_type = wanted_type;
wanted_type_ptr->wanted_type_name = wanted_type_name;
- wanted_type_ptr->pointer_count = fci->pointer_count + alloc_flag;
+ wanted_type_ptr->pointer_count = pfci->pointer_count + alloc_flag;
wanted_type_ptr->char_lenient_flag = 0;
- if (strchr (fci->flags2, 'c') != 0)
+ if (strchr (pfci->flags2, 'c') != 0)
wanted_type_ptr->char_lenient_flag = 1;
wanted_type_ptr->scalar_identity_flag = 0;
if (scalar_identity_flag)
@@ -2391,9 +3251,9 @@ check_format_info_main (format_check_results *res,
wanted_type_ptr->writing_in_flag = 1;
else
{
- if (strchr (fci->flags2, 'W') != 0)
+ if (strchr (pfci->flags2, 'W') != 0)
wanted_type_ptr->writing_in_flag = 1;
- if (strchr (fci->flags2, 'R') != 0)
+ if (strchr (pfci->flags2, 'R') != 0)
wanted_type_ptr->reading_from_flag = 1;
}
wanted_type_ptr->kind = CF_KIND_FORMAT;
@@ -2409,19 +3269,25 @@ check_format_info_main (format_check_results *res,
first_wanted_type = wanted_type_ptr;
last_wanted_type = wanted_type_ptr;
- fci = fci->chain;
- if (fci)
+ pfci = pfci->chain;
+ if (pfci)
{
wanted_type_ptr = fwt_pool.allocate ();
arg_num++;
- wanted_type = *fci->types[length_chars_val].type;
- wanted_type_name = fci->types[length_chars_val].name;
+ wanted_type = *pfci->types[length_chars_val].type;
+ wanted_type_name = pfci->types[length_chars_val].name;
}
}
}
if (first_wanted_type != 0)
check_format_types (format_string_loc, first_wanted_type);
+
+ if (warn_format_length)
+ check_format_length (format_string_loc, res, info, fci,
+ cvtbeg, format_chars - cvtbeg,
+ cvtbeg - orig_format_chars,
+ &cvtspec, cur_param);
}
if (format_chars - orig_format_chars != format_length)
@@ -2437,6 +3303,62 @@ check_format_info_main (format_check_results *res,
}
if (has_operand_number > 0)
finish_dollar_format_checking (res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK);
+
+ /* Bail early if format length checking is disabled, either
+ via a command line option, or as a result of the size of
+ the destination object not being available, or due to
+ formt directives or arguments encountered during processing
+ that prevent length tracking with any reliability. */
+ if (!warn_format_length
+ || SIZE_MAX / 2 < info->objsize
+ || res->number_chars_min < 0)
+ return;
+
+ /* Compute the number of available bytes in the destination. There
+ must always be at least one byte left for the terminating NUL that's
+ appended after the format string has been processed. */
+ size_t navail = bytes_remaining (info, res);
+
+ /* If the number of available bytes has wrapped around zero
+ the destination has already overflowed and been diagnosed so
+ avoid diagnosing it again. In the diagnostic, distinguish between
+ a possible overflow ("may write"), a certain overlow somewhere "past
+ the end", and a certain overflow into the byte "just past the end"
+ of the destination. (Ditto for truncation.) */
+ bool overflowed = SIZE_MAX / 2 <= navail;
+ if (!overflowed && navail == 0)
+ {
+ location_t loc
+ = location_from_offset (format_string_loc,
+ format_chars - orig_format_chars + 1);
+
+ bool boundrange = res->number_chars_min < 0
+ || (res->number_chars_min < res->number_chars_max);
+
+ const char* fmtstr = info->bounded
+ ? (res->number_chars < 0 && boundrange
+ ? "output may be truncated while copying format string "
+ "into a region of size %qlu"
+ : "output truncated while copying format string "
+ "into a region of size %qlu")
+ : (res->number_chars < 0 ? boundrange
+ ? "may write a terminating nul past the end "
+ "of a region of size %qlu"
+ : "writing a terminating nul past the end "
+ "of a region of size %qlu"
+ : "writing a terminating nul just past the end "
+ "of a region of size %qlu");
+
+ warning_at (loc, OPT_Wformat_length_, fmtstr,
+ (unsigned long)info->objsize);
+ }
+
+ /* Help the user figure out how big a buffer they need. */
+ if (warn_format_length && (overflowed || navail < 1))
+ inform (input_location,
+ "destination region size is %qwu bytes, minimum required %qwu",
+ (unsigned HOST_WIDE_INT)info->objsize,
+ (unsigned HOST_WIDE_INT)(info->objsize - navail + 1));
}
@@ -2617,6 +3539,124 @@ check_format_types (location_t loc, format_wanted_type *types)
}
}
+static void
+check_format_length (location_t loc,
+ format_check_results *res,
+ const function_format_info *info,
+ const format_char_info *fci,
+ const char *cvtbeg,
+ size_t cvtlen,
+ size_t offset,
+ const conversion_spec *cvtspec,
+ tree arg)
+{
+ /* Bail when there is no function to compute the output length,
+ or when the size of the object is too big (i.e., unknown)
+ or when minimum length checking has been disabled. */
+ if (!fci->fmtfunc
+ || SIZE_MAX / 2 < info->objsize
+ || res->number_chars_min == -1)
+ return;
+
+ /* Compute the (approximate) length of the formatted output. */
+ format_char_info::fmtresult fmtres = fci->fmtfunc (cvtspec, arg);
+
+ /* The overall result is bounded only if the output of every
+ directive is exact or bounded. */
+ res->bounded = fmtres.bounded;
+
+ if (fmtres.max < 0)
+ {
+ /* Disable exact and maximum length checking after a failure
+ to determine the maximum number of characters (for example
+ for wide characters or wide character strings) but continue
+ tracking the minimum number of characters. */
+ res->number_chars_max = -1;
+ res->number_chars = -1;
+ }
+
+ if (fmtres.min < 0)
+ {
+ /* Disable exact length checking after a failure to determine
+ even the minimum number of characters (it shouldn't happen
+ except in an error) but keep tracking the minimum and maximum
+ number of characters. */
+ res->number_chars = -1;
+ return;
+ }
+
+ /* Compute the number of available bytes in the destination. There
+ must always be at least one byte of space for the terminating
+ NUL that's appended after the format string has been processed. */
+ size_t navail = bytes_remaining (info, res);
+
+ /* The destination will have already overflowed if the number of
+ bytes has wrapped around zero. */
+ bool overflowed = SIZE_MAX / 2 <= navail;
+
+ if (fmtres.min < fmtres.max)
+ {
+ /* The result is a range (i.e., it's inexact). */
+ if (!overflowed)
+ {
+ loc = location_from_offset (loc, offset);
+
+ if (navail < (size_t)fmtres.min)
+ {
+ const char* fmtstr = info->bounded
+ ? "%<%%%.*s%> directive output truncated %qi bytes "
+ "into a region of size %qlu"
+ : "%<%%%.*s%> directive writing at least %qi bytes "
+ "into a region of size %qlu";
+ warning_at (loc, OPT_Wformat_length_, fmtstr,
+ (int)cvtlen, cvtbeg, fmtres.min,
+ (unsigned long)navail);
+ }
+ else if (navail < (size_t)fmtres.max
+ && (fmtres.bounded || 1 < warn_format_length))
+ {
+ const char* fmtstr = info->bounded
+ ? "%<%%%.*s%> directive output may be truncated between "
+ "%qi and %qi bytes into a region of size %qlu"
+ : "%<%%%.*s%> directive writing between %qi and %qi bytes "
+ "into a region of size %qlu";
+ warning_at (loc, OPT_Wformat_length_, fmtstr,
+ (int)cvtlen, cvtbeg, fmtres.min, fmtres.max,
+ (unsigned long)navail);
+ }
+ }
+
+ /* Disable exact length checking but adjust the minimum and maximum. */
+ res->number_chars = -1;
+ if (res->number_chars_max != -1 && fmtres.max != -1)
+ res->number_chars_max += fmtres.max;
+
+ res->number_chars_min += fmtres.min;
+ }
+ else
+ {
+ if (!overflowed && 0 < fmtres.min && navail < (size_t)fmtres.min)
+ {
+ loc = location_from_offset (loc, offset);
+ const char* fmtstr;
+ if (info->bounded)
+ fmtstr = 1 < fmtres.min
+ ? "%<%%%.*s%> directive output truncated while writing "
+ "%qi bytes into a region of size %qlu"
+ : "%<%%%.*s%> directive output truncated while writing "
+ "%qi byte into a region of size %qlu";
+ else
+ fmtstr = 1 < fmtres.min
+ ? "%<%%%.*s%> directive writing %qi bytes "
+ "into a region of size %qlu"
+ : "%<%%%.*s%> directive writing %qi byte "
+ "into a region of size %qlu";
+ warning_at (loc, OPT_Wformat_length_, fmtstr,
+ (int)cvtlen, cvtbeg, fmtres.min, (unsigned long)navail);
+ }
+ res->inc_number_chars (fmtres.min);
+ }
+}
/* Give a warning at LOC about a format argument of different type from that
expected. WANTED_TYPE is the type the argument should have, possibly
@@ -126,6 +126,7 @@ struct format_type_detail
#define BADLEN { STD_C89, NULL, NULL }
#define NOLENGTHS { BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
+struct conversion_spec;
/* Structure describing a format conversion specifier (or a set of specifiers
which act identically), and the length modifiers used with it. */
@@ -158,6 +159,17 @@ struct format_char_info
arguments, only POINTER_COUNT, TYPES, and the "c", "R", and "W" flags
in FLAGS2 are used. */
const struct format_char_info *chain;
+
+ /* Function to convert an argument for the format characters
+ in FORMAT_CHARS and return the number of characters. */
+ struct fmtresult {
+ int max, min;
+ /* True when the range is the result of an argument determined
+ to be bounded to a subrange of its type, false otherwise. */
+ bool bounded;
+ };
+
+ fmtresult (*fmtfunc)(const conversion_spec *, tree);
};
@@ -458,6 +458,11 @@ Wformat-extra-args
C ObjC C++ ObjC++ Var(warn_format_extra_args) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
Warn if passing too many arguments to a function for its format string.
+Wformat-length
+C ObjC C++ ObjC++ Warning Alias (Wformat-length=, 1, 0)
+Warn about function calls with format strings that wite past the end
+of the destination region.
+
Wformat-nonliteral
C ObjC C++ ObjC++ Var(warn_format_nonliteral) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 2, 0)
Warn about format strings that are not literals.
@@ -482,6 +487,11 @@ Wformat=
C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 1, 0)
Warn about printf/scanf/strftime/strfmon format string anomalies.
+Wformat-length=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format_length) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
+Warn about function calls with format strings that wite past the end
+of the destination region.
+
Wignored-qualifiers
C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra)
Warn whenever type qualifiers are ignored.
@@ -37,6 +37,8 @@ enum c_language_kind c_language = clk_c;
#define LANG_HOOKS_INIT c_objc_common_init
#undef LANG_HOOKS_INIT_TS
#define LANG_HOOKS_INIT_TS c_common_init_ts
+#undef LANG_HOOKS_CHECK_FORMAT_LENGTH
+#define LANG_HOOKS_CHECK_FORMAT_LENGTH check_function_format
/* Each front end provides its own lang hook initializer. */
struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
@@ -3090,7 +3090,7 @@ build_function_call_vec (location_t loc, vec<location_t> arg_loc,
return error_mark_node;
/* Check that the arguments to the function are valid. */
- check_function_arguments (loc, fntype, nargs, argarray);
+ check_function_arguments (loc, fundecl, fntype, nargs, argarray);
if (name != NULL_TREE
&& !strncmp (IDENTIFIER_POINTER (name), "__builtin_", 10))
@@ -7578,7 +7578,8 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
for (j = 0; j < nargs; j++)
fargs[j] = maybe_constant_value (argarray[j]);
- check_function_arguments (input_location, TREE_TYPE (fn), nargs, fargs);
+ check_function_arguments (input_location, fn, TREE_TYPE (fn),
+ nargs, fargs);
}
/* Avoid actually calling copy constructors and copy assignment operators,
@@ -78,6 +78,8 @@ static tree cxx_enum_underlying_base_type (const_tree);
#define LANG_HOOKS_EH_RUNTIME_TYPE build_eh_type_type
#undef LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE
#define LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE cxx_enum_underlying_base_type
+#undef LANG_HOOKS_CHECK_FORMAT_LENGTH
+#define LANG_HOOKS_CHECK_FORMAT_LENGTH check_function_format
/* Each front end provides its own lang hook initializer. */
struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
@@ -3641,7 +3641,7 @@ cp_build_function_call_vec (tree function, vec<tree, va_gc> **params,
/* Check for errors in format strings and inappropriately
null parameters. */
- check_function_arguments (input_location, fntype, nargs, argarray);
+ check_function_arguments (input_location, fndecl, fntype, nargs, argarray);
ret = build_cxx_call (function, nargs, argarray, complain);
@@ -265,7 +265,8 @@ Objective-C and Objective-C++ Dialects}.
-Wno-div-by-zero -Wdouble-promotion -Wduplicated-cond @gol
-Wempty-body -Wenum-compare -Wno-endif-labels @gol
-Werror -Werror=* -Wfatal-errors -Wfloat-equal -Wformat -Wformat=2 @gol
--Wno-format-contains-nul -Wno-format-extra-args -Wformat-nonliteral @gol
+-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=2 @gol
+-Wformat-nonliteral @gol
-Wformat-security -Wformat-signedness -Wformat-y2k -Wframe-address @gol
-Wframe-larger-than=@var{len} -Wno-free-nonheap-object -Wjump-misses-init @gol
-Wignored-qualifiers -Wignored-attributes -Wincompatible-pointer-types @gol
@@ -3773,6 +3774,80 @@ 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 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
@@ -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);
@@ -52,6 +52,7 @@ extern void lhd_print_error_function (diagnostic_context *,
const char *, struct diagnostic_info *);
extern void lhd_set_decl_assembler_name (tree);
extern bool lhd_warn_unused_global_decl (const_tree);
+extern void lhd_check_format_length (const_tree, const_tree, int, tree *);
extern void lhd_incomplete_type_error (location_t, const_tree, const_tree);
extern tree lhd_type_promotes_to (tree);
extern void lhd_register_builtin_type (tree, const char *);
@@ -211,6 +212,7 @@ extern tree lhd_make_node (enum tree_code);
#define LANG_HOOKS_FUNCTION_DECL_EXPLICIT_P hook_bool_tree_false
#define LANG_HOOKS_FUNCTION_DECL_DELETED_P hook_bool_tree_false
#define LANG_HOOKS_WARN_UNUSED_GLOBAL_DECL lhd_warn_unused_global_decl
+#define LANG_HOOKS_CHECK_FORMAT_LENGTH lhd_check_format_length
#define LANG_HOOKS_POST_COMPILATION_PARSING_CLEANUPS NULL
#define LANG_HOOKS_DECL_OK_FOR_SIBCALL lhd_decl_ok_for_sibcall
#define LANG_HOOKS_OMP_PRIVATIZE_BY_REFERENCE hook_bool_const_tree_false
@@ -236,6 +238,7 @@ extern tree lhd_make_node (enum tree_code);
LANG_HOOKS_FUNCTION_PARM_EXPANDED_FROM_PACK_P, \
LANG_HOOKS_GET_GENERIC_FUNCTION_DECL, \
LANG_HOOKS_WARN_UNUSED_GLOBAL_DECL, \
+ LANG_HOOKS_CHECK_FORMAT_LENGTH, \
LANG_HOOKS_POST_COMPILATION_PARSING_CLEANUPS, \
LANG_HOOKS_DECL_OK_FOR_SIBCALL, \
LANG_HOOKS_OMP_PRIVATIZE_BY_REFERENCE, \
@@ -136,6 +136,14 @@ lhd_warn_unused_global_decl (const_tree decl)
return true;
}
+void
+lhd_check_format_length (const_tree ARG_UNUSED (fndecl),
+ const_tree ARG_UNUSED (attrs),
+ int ARG_UNUSED (nargs),
+ tree * ARG_UNUSED (argarray))
+{
+}
+
/* Set the DECL_ASSEMBLER_NAME for DECL. */
void
lhd_set_decl_assembler_name (tree decl)
@@ -201,6 +201,9 @@ struct lang_hooks_for_decls
We will already have checked that it has static binding. */
bool (*warn_unused_global) (const_tree);
+ /* Check the length of output produced by sprintf-like functions. */
+ void (*check_format_length) (const_tree, const_tree, int, tree *);
+
/* Perform any post compilation-proper parser cleanups and
processing. This is currently only needed for the C++ parser,
which hopefully can be cleaned up so this hook is no longer
@@ -2425,8 +2425,15 @@ execute_pass_list_1 (opt_pass *pass)
if (cfun == NULL)
return;
+
+ // inform (0, "executing pass: %s", pass->name);
+
if (execute_one_pass (pass) && pass->sub)
- execute_pass_list_1 (pass->sub);
+ {
+ // inform (0, "executing subpass: %s", pass->sub->name);
+ execute_pass_list_1 (pass->sub);
+ }
+
pass = pass->next;
}
while (pass);
new file mode 100644
@@ -0,0 +1,32 @@
+/* PR c++/71675 - __atomic_compare_exchange_n returns wrong type for typed enum
+ */
+/* { dg-do compile { target c11 } } */
+
+#define Test(T) \
+ do { \
+ static T x; \
+ int r [_Generic (__atomic_compare_exchange_n (&x, &x, x, 0, 0, 0), \
+ _Bool: 1, default: -1)]; \
+ (void)&r; \
+ } while (0)
+
+void f (void)
+{
+ /* __atomic_compare_exchange_n would fail to return _Bool when
+ its arguments were one of the three character types. */
+ Test (char);
+ Test (signed char);
+ Test (unsigned char);
+
+ Test (int);
+ Test (unsigned int);
+
+ Test (long);
+ Test (unsigned long);
+
+ Test (long long);
+ Test (unsigned long long);
+
+ typedef enum E { e } E;
+ Test (E);
+}
new file mode 100644
@@ -0,0 +1,852 @@
+/* { 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. */
+#ifndef LINE
+# define LINE 0
+#endif
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+#define T(bufsize, fmt, ...) \
+ __builtin___sprintf_chk (buffer, 0, \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+
+char buffer [256];
+
+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";
+
+/* Exercise the "%c" and "%lc" directive. */
+
+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 just past the end of a region of size .1." } */
+ T (1, "%c", '1'); /* { dg-warning "nul just past the end" } */
+ T (2, "%c", '1');
+ T (2, "%2c", '1'); /* { dg-warning "nul just past the end" } */
+ T (2, "%3c", '1'); /* { dg-warning "into a region" } */
+ T (2, "%c%c", '1', '2'); /* { dg-warning "nul just 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 just 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. */
+
+void test_sprintf_chk_s_const (void)
+{
+ T (0, "%*s", 0, ""); /* { dg-warning "nul just past the end" } */
+ T (0, "%*s", 0, s0); /* { dg-warning "nul just 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 just past the end" } */
+ T (1, "%*s", 0, s1); /* { dg-warning "nul just past the end" } */
+ T (1, "%1s", ""); /* { dg-warning "nul just past the end" } */
+ T (1, "%1s", s0); /* { dg-warning "nul just past the end" } */
+ T (1, "%*s", 1, ""); /* { dg-warning "nul just past the end" } */
+ T (1, "%*s", 1, s0); /* { dg-warning "nul just 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 just past the end" } */
+ T (1, "%.1s", s3); /* { dg-warning "nul just past the end" } */
+ T (1, "%.*s", 1, "123"); /* { dg-warning "nul just past the end" } */
+ T (1, "%.*s", 1, s3); /* { dg-warning "nul just 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"); /* { dg-warning "nul just past the end" } */
+ T (2, "%*s", 0, s2); /* { dg-warning "nul just past the end" } */
+
+ T (1, "%s%s", "", "");
+ T (1, "%s%s", s0, s0);
+ T (1, "%s%s", "", "1"); /* { dg-warning "nul just past the end" } */
+ T (1, "%s%s", s0, s1); /* { dg-warning "nul just past the end" } */
+ T (1, "%s%s", "1", ""); /* { dg-warning "nul just past the end" } */
+ T (1, "%s%s", s1, s0); /* { dg-warning "nul just 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 just past the end" } */
+ T (2, "%s%s", "1", "2"); /* { dg-warning "nul just 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 just past the end" } */
+ T (2, "%%%s", "1"); /* { dg-warning "nul just past the end" } */
+ T (2, "%s%%", "1"); /* { dg-warning "nul just past the end" } */
+ T (2, "_%s", "12"); /* { dg-warning "into a region" } */
+ T (2, "__%s", "1"); /* { dg-warning "into a region" } */
+
+ T (3, "__%s", "");
+ T (3, "__%s", "1"); /* { dg-warning "nul just 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 just 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, "%.2ls", L"1"); /* { dg-warning "may write a terminating 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. */
+
+void test_sprintf_chk_hh_const (void)
+{
+ T (1, "%hhd", 0); /* { dg-warning "nul just past the end" } */
+ T (1, "%hhd", 1); /* { dg-warning "nul just 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 just past the end" } */
+
+ T (1, "%hhi", 0); /* { dg-warning "nul just past the end" } */
+ T (1, "%hhi", 1); /* { dg-warning "nul just 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 just past the end" } */
+
+ T (2, "%hhi", 0);
+ T (2, "%hhi", 1);
+ T (2, "%hhi", 9);
+ T (2, "% hhi", 9); /* { dg-warning "nul just past the end" } */
+ T (2, "%+hhi", 9); /* { dg-warning "nul just past the end" } */
+ T (2, "%-hhi", 9);
+ T (2, "%hhi", 10); /* { dg-warning "nul just past the end" } */
+ T (2, "%hhi", -1); /* { dg-warning "nul just past the end" } */
+ T (2, "% hhi", -1); /* { dg-warning "nul just past the end" } */
+ T (2, "%+hhi", -1); /* { dg-warning "nul just past the end" } */
+ T (2, "%-hhi", -1); /* { dg-warning "nul just past the end" } */
+
+ T (2, "%hho", 0);
+ T (2, "%hho", 1);
+ T (2, "%hho", 7);
+ T (2, "%hho", 010); /* { dg-warning "nul just past the end" } */
+ T (2, "%hho", 077); /* { dg-warning "nul just 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 just past the end" } */
+ T (2, "%hhX", 0xf);
+ T (2, "%hhx", 0x10); /* { dg-warning "nul just past the end" } */
+ T (2, "%hhX", 0xff); /* { dg-warning "nul just past the end" } */
+
+ T (1, "%#hhx", 0); /* { dg-warning "nul just past the end" } */
+ T (2, "%#hhx", 0);
+ T (3, "%#hhx", 1); /* { dg-warning "nul just 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 just past the end" } */
+ T (4, "%#hhx", 0xf);
+ T (4, "%#hhx", 0x10); /* { dg-warning "nul just past the end" } */
+ T (4, "%#hhx", 0xff); /* { dg-warning "nul just past the end" } */
+ T (4, "%#hhx", 0xfff); /* { dg-warning "nul just past the end" } */
+
+ T (4, "%hhi %hhi", 0, 0);
+ T (4, "%hhi %hhi", 9, 9);
+ T (4, "%hhi %hhi", 1, 10); /* { dg-warning "nul just past the end" } */
+ T (4, "%hhi %hhi", 10, 1); /* { dg-warning "nul just past the end" } */
+ T (4, "%hhi %hhi", 11, 12); /* { dg-warning "into a region" } */
+
+ /* 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" } */
+}
+
+void test_sprintf_chk_h_const (void)
+{
+ T (1, "%hu", 0); /* { dg-warning "nul just past the end" } */
+ T (1, "%hu", 1); /* { dg-warning "nul just 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 just past the end" } */
+ T (2, "%+hi", 9); /* { dg-warning "nul just past the end" } */
+ T (2, "%-hi", 9);
+ T (2, "%hi", 10); /* { dg-warning "nul just past the end" } */
+ T (2, "%hi", -1); /* { dg-warning "nul just past the end" } */
+ T (2, "% hi", -2); /* { dg-warning "nul just past the end" } */
+ T (2, "%+hi", -3); /* { dg-warning "nul just past the end" } */
+ T (2, "%-hi", -4); /* { dg-warning "nul just past the end" } */
+
+ T (2, "%hu", 0);
+ T (2, "%hu", 1);
+ T (2, "%hu", 9);
+ T (2, "%hu", 10); /* { dg-warning "nul just 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 just past the end" } */
+ T (2, "%ho", 077); /* { dg-warning "nul just 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 just past the end" } */
+ T (2, "%hx", 0xff); /* { dg-warning "nul just 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 just past the end" } */
+ T (5, "%hu", 65535); /* { dg-warning "nul just past the end" } */
+
+ T (1, "%#hx", 0); /* { dg-warning "nul just past the end" } */
+ T (2, "%#hx", 0);
+ T (3, "%#hx", 1); /* { dg-warning "nul just past the end" } */
+
+ T (4, "%#hx", 0);
+ T (4, "%#hx", 1);
+ T (4, "%#hx", 0xf);
+ T (4, "%#hx", 0x10); /* { dg-warning "nul just past the end" } */
+ T (4, "%#hx", 0xff); /* { dg-warning "nul just 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 just past the end" } */
+ T (1, "%hhu", 1); /* { dg-warning "nul just 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 just past the end" } */
+}
+
+void test_sprintf_chk_integer_const (void)
+{
+ T ( 1, "%i", 0); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%i", 1); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%i", -1); /* { dg-warning "into a region" } */
+ T ( 1, "%i_", 1); /* { dg-warning "character ._. at offset .2. just 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 just past the end" } */
+ T ( 1, "%u", 0); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%x", 0); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%#x", 0); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%x", 1); /* { dg-warning "nul just 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 just past the end" } */
+ T ( 2, "%i", 10); /* { dg-warning "nul just past the end" } */
+ T ( 2, "%i_", 0); /* { dg-warning "nul just past the end" } */
+ T ( 2, "_%i", 0); /* { dg-warning "nul just past the end" } */
+ T ( 2, "_%i_", 0); /* { dg-warning "character ._. at offset .3. just past the end" } */
+ T ( 2, "%o", 1);
+ T ( 2, "%o", 7);
+ T ( 2, "%o", 010); /* { dg-warning "nul just 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 just past the end" } */
+ T ( 2, "%x", 0xff); /* { dg-warning "nul just 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 just 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 just past the end" } */
+ T ( 9, "%8u", 1);
+
+#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 just past the end" } */
+ T (10, "%i", MAX); /* { dg-warning "nul just past the end" } */
+ T (10, "%i", MIN); /* { dg-warning "into a region" } */
+
+ T (11, "%i", MAX);
+ T (11, "%i", MIN); /* { dg-warning "nul just past the end" } */
+}
+
+void test_sprintf_chk_l_const (void)
+{
+ T ( 1, "%li", 0L); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%li", 1L); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%li", -1L); /* { dg-warning "into a region" } */
+ T ( 1, "%li_", 1L); /* { dg-warning "character ._. at offset .3. just past the end" } */
+ T ( 1, "_%li", 1L); /* { dg-warning "into a region" } */
+ T ( 1, "_%li_", 1L); /* { dg-warning "into a region" } */
+}
+
+void test_sprintf_chk_z_const (void)
+{
+ T ( 1, "%zi", (size_t)0); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%zi", (size_t)1); /* { dg-warning "nul just 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. just 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 just 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 just past the end" } */
+ T (12, "%e", 12.0); /* { dg-warning "nul just past the end" } */
+ T (13, "%e", 1.3);
+ T (13, "%E", 13.0);
+ T (13, "%e", 13.0);
+
+ T ( 5, "%.0e", 0.0); /* { dg-warning "nul just past the end" } */
+ T ( 5, "%.0e", 1.0); /* { dg-warning "nul just 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 (const char *s)
+{
+ T (0, "%s", s); /* { dg-warning "nul past the end" } */
+ T (1, "%s", s);
+ /* 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 "just 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 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)
+{
+ T (0, "%E", d); /* { dg-warning "writing at least .13. 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 "into a region" } */
+ T (12, "%e", d); /* { dg-warning "into a region" } */
+ T (13, "%E", d); /* { dg-warning "nul past the end" } */
+ T (13, "%e", d); /* { dg-warning "nul past the end" } */
+ T (14, "%E", d);
+ T (14, "%e", d);
+
+ T ( 5, "%.0e", d); /* { dg-warning "directive writing at least .6. bytes into a region of size .5." } */
+ T ( 6, "%.0e", d); /* { dg-warning "nul past the end" } */
+ T ( 7, "%.0e", 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(bufsize, fmt) \
+ __builtin___vsprintf_chk (buffer, 0, \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, 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 just past the end of a region of size .1." } */
+ T (1, "%c"); /* { dg-warning "nul just past the end" } */
+ T (2, "%c");
+ T (2, "%2c"); /* { dg-warning "nul just past the end" } */
+ T (2, "%3c"); /* { dg-warning "into a region" } */
+ T (2, "%c%c"); /* { dg-warning "nul just 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 just 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(bufsize, fmt, ...) \
+ __builtin_snprintf (buffer, \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, 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 while copying format string into a region of size .1." } */
+ 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 while copying format string" } */
+ 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 while copying format string" } */
+
+ 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(bufsize, fmt, ...) \
+ __builtin___snprintf_chk (buffer, bufsize, 0, \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, 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 "destination size .3. exceeds the size of the object .2." } */
+
+ 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 while copying format string into a region of size .1." } */
+ 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 while copying format string" } */
+ 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 while copying format string" } */
+
+ 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(bufsize, fmt) \
+ __builtin_vsnprintf (buffer, \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, va)
+
+void test_vsnprintf_s (__builtin_va_list va)
+{
+ T (0, "%s");
+ T (1, "%s");
+ T (1, "%1s"); /* { dg-warning "output truncated while copying format string" } */
+
+ T (2, "%s%s");
+ T (2, "%s%s_");
+ T (2, "%s_%s");
+ T (2, "_%s%s");
+ T (2, "_%s_%s"); /* { dg-warning "output truncated while copying format string" } */
+}
+
+#undef T
+#define T(bufsize, fmt) \
+ __builtin___vsnprintf_chk (buffer, bufsize, 0, \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, 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 "destination size .123. exceeds the size of the object .122." } */
+
+ __builtin___snprintf_chk (buffer, __SIZE_MAX__, 0, 2, " "); /* { dg-warning "destination size .* too large" } */
+
+ T (0, "%s");
+ T (1, "%s");
+ T (1, "%1s"); /* { dg-warning "output truncated while copying format string" } */
+
+ T (2, "%s%s");
+ T (2, "%s%s_");
+ T (2, "%s_%s");
+ T (2, "_%s%s");
+ T (2, "_%s_%s"); /* { dg-warning "output truncated while copying format string" } */
+}
new file mode 100644
@@ -0,0 +1,183 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
+
+/* Define line to the line number of the test case to exercise and
+ avoid exercising all the others. */
+#ifndef LINE
+# define LINE 0
+#endif
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+#if 1 // TEST_SPRINTF_CHK
+# define T(bufsize, fmt, ...) \
+ __builtin___sprintf_chk (buffer, 0, \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+#elif TEST_VSNPRINTF_CHK
+# define T(bufsize, fmt, ...) \
+ __builtin___vsnprintf_chk (va, buffer, sizeof buffer, 0 \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, va)
+#elif TEST_VSNPRINTF
+# define T(bufsize, fmt, ...) \
+ __builtin_vsnprintf (va, buffer, \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+#else
+# define T(bufsize, fmt, ...) \
+ __builtin_snprintf (buffer, \
+ (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+#endif
+
+__builtin_va_list va;
+
+char buffer [256];
+
+/* 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 following case is diagnosed at level 2 but not at level 1. */
+ T (2, "%.2ls", L"1"); /* { dg-warning "may write a terminating 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");
+}
+
+/* Exercise buffer overflow detection with non-const string arguments. */
+
+void test_s_nonconst (const char *s, const wchar_t *ws)
+{
+ T (0, "%s", s); /* { dg-warning "into a region" } */
+ T (1, "%s", s); /* { dg-warning "nul past the end" } */
+ 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" } */
+}
+
+ /* 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 a region of size .4." } */
+}
+
+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) int 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 a region of size .3." } */
+ 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 just past the end" } */
+ T (1, "%i", abf [x].b1); /* { dg-warning "nul just past the end" } */
+ T (1, "%i", pbf->b1); /* { dg-warning "nul just 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,186 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+#ifndef LINE
+# define LINE 0
+#endif
+
+#define SCHAR_MAX __SCHAR_MAX__
+#define SCHAR_MIN (-SCHAR_MAX - 1)
+
+#define bos(x) \
+ ((!LINE || __LINE__ == LINE) ? __builtin_object_size (x, 0) : __SIZE_MAX__)
+
+#define T(bufsize, fmt, ...) \
+ do { \
+ char *d = (char *)__builtin_malloc (bufsize); \
+ __builtin___sprintf_chk (d, 0, bos (d), fmt, __VA_ARGS__); \
+ sink (d); \
+ } while (0)
+
+void __attribute__ ((noclone, noinline))
+sink (void *p)
+{
+ __builtin_free (p);
+}
+
+/* Identity function to require optimization to figure out the value
+ of the operand. */
+int i (int x) { return x; }
+const char* s (const char *str) { return str; }
+
+void test_sprintf_chk_integer_value (void)
+{
+ T ( 1, "%i", i ( 0)); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%i", i ( 1)); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%i", i ( -1)); /* { dg-warning "into a region" } */
+ T ( 1, "%i_", i ( 1)); /* { dg-warning "character ._. at offset .2. just 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 just past the end" } */
+ T ( 1, "%u", i ( 0)); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%x", i ( 0)); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%#x", i ( 0)); /* { dg-warning "nul just past the end" } */
+ T ( 1, "%x", i ( 1)); /* { dg-warning "nul just 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 just past the end" } */
+ T ( 2, "%i", i ( 10)); /* { dg-warning "nul just past the end" } */
+ T ( 2, "%i_", i ( 0)); /* { dg-warning "nul just past the end" } */
+ T ( 2, "_%i", i ( 0)); /* { dg-warning "nul just past the end" } */
+ T ( 2, "_%i_",i ( 0)); /* { dg-warning "character ._. at offset .3. just past the end" } */
+ T ( 2, "%o", i ( 1));
+ T ( 2, "%o", i ( 7));
+ T ( 2, "%o", i ( 010)); /* { dg-warning "nul just 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 just past the end" } */
+ T ( 2, "%x", i ( 0xff)); /* { dg-warning "nul just 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 just past the end" } */
+
+ T ( 8, "%8u", i ( 1)); /* { dg-warning "nul just past the end" } */
+ T ( 9, "%8u", i ( 1));
+}
+
+/* Functions to require optimization to figure out the range of the operand.
+ Used to verify that the checker makes use of the range information to
+ avoid diagnosing the output of sufficiently constrained arguments to
+ integer directives. */
+
+signed char*
+range_schar (signed char *val, signed char min, signed char max)
+{
+ if (*val < min || max < *val) __builtin_abort ();
+ return val;
+}
+
+unsigned char*
+range_uchar (unsigned char *val, unsigned char min, unsigned char max)
+{
+ if (*val < min || max < *val) __builtin_abort ();
+ return val;
+}
+
+signed short*
+range_sshort (signed short *val, signed short min, signed short max)
+{
+ if (*val < min || max < *val) __builtin_abort ();
+ return val;
+}
+
+unsigned short*
+range_ushort (unsigned short *val, unsigned short min, unsigned short max)
+{
+ if (*val < min || max < *val) __builtin_abort ();
+ return val;
+}
+
+static int idx;
+
+/* Exercise ranges only in types signed and unsigned char and short.
+ No other types work due to bug 71690. */
+
+void test_sprintf_chk_range_schar (signed char *a)
+{
+ /* Ra creates a range of signed char for A [idx]. A different
+ value is used each time to prevent the ranges from intesecting
+ one another, possibly even eliminating some tests as a result
+ of the range being empty. */
+#define R(min, max) *range_schar (a + idx++, min, max)
+
+ T ( 0, "%i", R (0, 9)); /* { dg-warning ".%i. directive writing .1. byte into a region of size .0." } */
+ T ( 1, "%i", R (0, 9)); /* { dg-warning "nul just 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 a region of size .2." } */
+ T ( 2, "%i", R (9, 10)); /* { dg-warning "may write a terminating nul past the end of a region of size .2." } */
+
+ T ( 3, "%i", R (-9, 9));
+ T ( 3, "%i", R ( 0, 99));
+ T ( 3, "%i", R ( 0, 100)); /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+
+ /* 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 just past the end" } */
+
+ T ( 5, "%i%i", R (-9, 99), R (-9, 99));
+
+ T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 9));
+ T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 10)); /* { dg-warning "may write a terminating nul past the end" } */
+ T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 9)); /* { dg-warning "may write a terminating nul past the end" } */
+ T ( 6, "%i_%i_%i", R (0, 10), R (0, 9), R (0, 9)); /* { dg-warning "may write a terminating nul past the end" } */
+ T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning ".%i. directive writing between .1. and .2. bytes into a region of size .1." } */
+}
+
+void test_sprintf_chk_range_uchar (unsigned char *a, unsigned char *b)
+{
+#undef Ra
+#define Ra(min, max) *range_uchar (a + __LINE__, 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 just 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 a region of size .2." } */
+
+ T ( 3, "%i", Ra (0, 99));
+ T ( 3, "%i", Ra (0, 100)); /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+}
+
+void test_sprintf_chk_range_sshort (signed short *a, signed short *b)
+{
+#undef Ra
+#define Ra(min, max) *range_sshort (a + __LINE__, 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 just past the end" } */
+ T ( 1, "%i", Ra ( 0, 9)); /* { dg-warning "nul just 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 a region of size .2." } */
+ T ( 2, "%i", Ra ( 9, 10)); /* { dg-warning "may write a terminating nul past the end of a region of size .2." } */
+
+ T ( 3, "%i", Ra ( 0, 99));
+ T ( 3, "%i", Ra (99, 999)); /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+
+ 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 a region of size .4." } */
+}