diff options
author | msebor <msebor@138bc75d-0d04-0410-961f-82ee72b054a4> | 2016-12-08 23:50:40 +0000 |
---|---|---|
committer | msebor <msebor@138bc75d-0d04-0410-961f-82ee72b054a4> | 2016-12-08 23:50:40 +0000 |
commit | 370e45b9887b6603911bbe1776c556d2404455bf (patch) | |
tree | 1265e2a8ec25f7020ef6c9eb300baef80c2e2afa /gcc/calls.c | |
parent | 479d1189cd705a30e3a247c4170794b29c3adc42 (diff) | |
download | gcc-370e45b9887b6603911bbe1776c556d2404455bf.tar.gz |
PR c/77531 - __attribute__((alloc_size(1,2))) could also warn on multiplication overflow
PR c/78284 - warn on malloc with very large arguments
gcc/c-family/ChangeLog:
PR c/78284
* c.opt (-Walloc-zero, -Walloc-size-larger-than): New options.
gcc/ChangeLog:
PR c/78284
* builtin-attrs.def (ATTR_ALLOC_SIZE, ATTR_RETURNS_NONNULL): New
identifier tree nodes.
(ATTR_ALLOCA_SIZE_1_NOTHROW_LEAF_LIST): New attribute list.
(ATTR_MALLOC_SIZE_1_NOTHROW_LIST): Same.
(ATTR_MALLOC_SIZE_1_NOTHROW_LEAF_LIST): Same.
(ATTR_MALLOC_SIZE_1_2_NOTHROW_LEAF_LIST): Same.
(ATTR_ALLOC_SIZE_2_NOTHROW_LEAF_LIST): Same.
* builtins.c (expand_builtin_alloca): Call
maybe_warn_alloc_args_overflow.
* builtins.def (aligned_alloc, calloc, malloc, realloc):
Add attribute alloc_size.
(alloca): Add attribute alloc_size and returns_nonnull.
* calls.h (maybe_warn_alloc_args_overflow): Declare.
* calls.c (alloc_max_size, operand_signed_p): New functions.
(maybe_warn_alloc_args_overflow): Define.
(initialize_argument_information): Diagnose overflow in functions
declared with attaribute alloc_size.
* doc/invoke.texi (Warning Options): Document -Walloc-zero and
-Walloc-size-larger-than.
gcc/testsuite/ChangeLog:
PR c/78284
* gcc.dg/attr-alloc_size-3.c: New test.
* gcc.dg/attr-alloc_size-4.c: New test.
* gcc.dg/attr-alloc_size-5.c: New test.
* gcc.dg/attr-alloc_size-6.c: New test.
* gcc.dg/attr-alloc_size-7.c: New test.
* gcc.dg/attr-alloc_size-8.c: New test.
* gcc.dg/attr-alloc_size-9.c: New test.
* gcc/testsuite/gcc.dg/errno-1.c: Adjust.
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@243470 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'gcc/calls.c')
-rw-r--r-- | gcc/calls.c | 341 |
1 files changed, 340 insertions, 1 deletions
diff --git a/gcc/calls.c b/gcc/calls.c index 530e7bf219d..bc3cbd599fc 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -48,8 +48,10 @@ along with GCC; see the file COPYING3. If not see #include "dbgcnt.h" #include "rtl-iter.h" #include "tree-chkp.h" +#include "tree-vrp.h" +#include "tree-ssanames.h" #include "rtl-chkp.h" - +#include "intl.h" /* Like PREFERRED_STACK_BOUNDARY but in units of bytes, not bits. */ #define STACK_BYTES (PREFERRED_STACK_BOUNDARY / BITS_PER_UNIT) @@ -1181,6 +1183,311 @@ store_unaligned_arguments_into_pseudos (struct arg_data *args, int num_actuals) } } +/* The limit set by -Walloc-larger-than=. */ +static GTY(()) tree alloc_object_size_limit; + +/* Initialize ALLOC_OBJECT_SIZE_LIMIT based on the -Walloc-size-larger-than= + setting if the option is specified, or to the maximum object size if it + is not. Return the initialized value. */ + +static tree +alloc_max_size (void) +{ + if (!alloc_object_size_limit) + { + alloc_object_size_limit = TYPE_MAX_VALUE (ssizetype); + + unsigned HOST_WIDE_INT unit = 1; + + char *end; + errno = 0; + unsigned HOST_WIDE_INT limit + = warn_alloc_size_limit ? strtoull (warn_alloc_size_limit, &end, 10) : 0; + + if (limit && !errno) + { + if (end && *end) + { + /* Numeric option arguments are at most INT_MAX. Make it + possible to specify a larger value by accepting common + suffixes. */ + if (!strcmp (end, "kB")) + unit = 1000; + else if (!strcasecmp (end, "KiB") || strcmp (end, "KB")) + unit = 1024; + else if (!strcmp (end, "MB")) + unit = 1000LU * 1000; + else if (!strcasecmp (end, "MiB")) + unit = 1024LU * 1024; + else if (!strcasecmp (end, "GB")) + unit = 1000LU * 1000 * 1000; + else if (!strcasecmp (end, "GiB")) + unit = 1024LU * 1024 * 1024; + else if (!strcasecmp (end, "TB")) + unit = 1000LU * 1000 * 1000 * 1000; + else if (!strcasecmp (end, "TiB")) + unit = 1024LU * 1024 * 1024 * 1024; + else if (!strcasecmp (end, "PB")) + unit = 1000LU * 1000 * 1000 * 1000 * 1000; + else if (!strcasecmp (end, "PiB")) + unit = 1024LU * 1024 * 1024 * 1024 * 1024; + else if (!strcasecmp (end, "EB")) + unit = 1000LU * 1000 * 1000 * 1000 * 1000 * 1000; + else if (!strcasecmp (end, "EiB")) + unit = 1024LU * 1024 * 1024 * 1024 * 1024 * 1024; + else + unit = 0; + } + + if (unit) + alloc_object_size_limit = build_int_cst (ssizetype, limit * unit); + } + } + return alloc_object_size_limit; +} + +/* Return true if the type of OP is signed, looking through any casts + to an unsigned type. */ + +static bool +operand_signed_p (tree op) +{ + if (TREE_CODE (op) == SSA_NAME) + { + gimple *def = SSA_NAME_DEF_STMT (op); + if (is_gimple_assign (def)) + { + /* In an assignment involving a cast, ignore the type + of the cast and consider the type of its operand. */ + tree_code code = gimple_assign_rhs_code (def); + if (code == NOP_EXPR) + op = gimple_assign_rhs1 (def); + } + else if (gimple_code (def) == GIMPLE_PHI) + { + /* In a phi, a constant argument may be unsigned even + if in the source it's signed and negative. Ignore + those and consider the result of a phi signed if + all its non-constant operands are. */ + unsigned nargs = gimple_phi_num_args (def); + for (unsigned i = 0; i != nargs; ++i) + { + tree op = gimple_phi_arg_def (def, i); + if (TREE_CODE (op) != INTEGER_CST + && !operand_signed_p (op)) + return false; + } + + return true; + } + } + + return !TYPE_UNSIGNED (TREE_TYPE (op)); +} + +/* Diagnose a call EXP to function FN decorated with attribute alloc_size + whose argument numbers given by IDX with values given by ARGS exceed + the maximum object size or cause an unsigned oveflow (wrapping) when + multiplied. When ARGS[0] is null the function does nothing. ARGS[1] + may be null for functions like malloc, and non-null for those like + calloc that are decorated with a two-argument attribute alloc_size. */ + +void +maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2]) +{ + /* The range each of the (up to) two arguments is known to be in. */ + tree argrange[2][2] = { { NULL_TREE, NULL_TREE }, { NULL_TREE, NULL_TREE } }; + + /* Maximum object size set by -Walloc-size-larger-than= or SIZE_MAX / 2. */ + tree maxobjsize = alloc_max_size (); + + location_t loc = EXPR_LOCATION (exp); + + bool warned = false; + + /* Validate each argument individually. */ + for (unsigned i = 0; i != 2 && args[i]; ++i) + { + if (TREE_CODE (args[i]) == INTEGER_CST) + { + argrange[i][0] = args[i]; + argrange[i][1] = args[i]; + + if (tree_int_cst_lt (args[i], integer_zero_node)) + { + warned = warning_at (loc, OPT_Walloc_size_larger_than_, + "argument %i value %qE is negative", + idx[i] + 1, args[i]); + } + else if (integer_zerop (args[i])) + { + /* Avoid issuing -Walloc-zero for allocation functions other + than __builtin_alloca that are declared with attribute + returns_nonnull because there's no portability risk. This + avoids warning for such calls to libiberty's xmalloc and + friends. + Also avoid issuing the warning for calls to function named + "alloca". */ + if ((DECL_FUNCTION_CODE (fn) == BUILT_IN_ALLOCA + && IDENTIFIER_LENGTH (DECL_NAME (fn)) != 6) + || (DECL_FUNCTION_CODE (fn) != BUILT_IN_ALLOCA + && !lookup_attribute ("returns_nonnull", + TYPE_ATTRIBUTES (TREE_TYPE (fn))))) + warned = warning_at (loc, OPT_Walloc_zero, + "argument %i value is zero", + idx[i] + 1); + } + else if (tree_int_cst_lt (maxobjsize, args[i])) + { + /* G++ emits calls to ::operator new[](SIZE_MAX) in C++98 + mode and with -fno-exceptions as a way to indicate array + size overflow. There's no good way to detect C++98 here + so avoid diagnosing these calls for all C++ modes. */ + if (i == 0 + && !args[1] + && lang_GNU_CXX () + && DECL_IS_OPERATOR_NEW (fn) + && integer_all_onesp (args[i])) + continue; + + warned = warning_at (loc, OPT_Walloc_size_larger_than_, + "argument %i value %qE exceeds " + "maximum object size %E", + idx[i] + 1, args[i], maxobjsize); + } + } + else if (TREE_CODE (args[i]) == SSA_NAME) + { + tree type = TREE_TYPE (args[i]); + + wide_int min, max; + value_range_type range_type = get_range_info (args[i], &min, &max); + if (range_type == VR_RANGE) + { + argrange[i][0] = wide_int_to_tree (type, min); + argrange[i][1] = wide_int_to_tree (type, max); + } + else if (range_type == VR_ANTI_RANGE) + { + /* For an anti-range, if the type of the formal argument + is unsigned and the bounds of the range are of opposite + signs when interpreted as signed, check to see if the + type of the actual argument is signed. If so, the lower + bound must be taken to be zero (rather than a large + positive value corresonding to the actual lower bound + interpreted as unsigned) and there is nothing else that + can be inferred from it. */ + --min; + ++max; + wide_int zero = wi::uhwi (0, TYPE_PRECISION (type)); + if (TYPE_UNSIGNED (type) + && wi::lts_p (zero, min) && wi::lts_p (max, zero) + && operand_signed_p (args[i])) + continue; + + argrange[i][0] = wide_int_to_tree (type, max); + argrange[i][1] = wide_int_to_tree (type, min); + + /* Verify that the anti-range doesn't make all arguments + invalid (treat the anti-range ~[0, 0] as invalid). */ + if (tree_int_cst_lt (maxobjsize, argrange[i][0]) + && tree_int_cst_le (argrange[i][1], integer_zero_node)) + { + warned + = warning_at (loc, OPT_Walloc_size_larger_than_, + (TYPE_UNSIGNED (type) + ? G_("argument %i range [%E, %E] exceeds " + "maximum object size %E") + : G_("argument %i range [%E, %E] is both " + "negative and exceeds maximum object " + "size %E")), + idx[i] + 1, argrange[i][0], + argrange[i][1], maxobjsize); + } + continue; + } + else + continue; + + /* Verify that the argument's range is not negative (including + upper bound of zero). */ + if (tree_int_cst_lt (argrange[i][0], integer_zero_node) + && tree_int_cst_le (argrange[i][1], integer_zero_node)) + { + warned = warning_at (loc, OPT_Walloc_size_larger_than_, + "argument %i range [%E, %E] is negative", + idx[i] + 1, argrange[i][0], argrange[i][1]); + } + else if (tree_int_cst_lt (maxobjsize, argrange[i][0])) + { + warned = warning_at (loc, OPT_Walloc_size_larger_than_, + "argument %i range [%E, %E] exceeds " + "maximum object size %E", + idx[i] + 1, argrange[i][0], argrange[i][1], + maxobjsize); + } + } + } + + if (!argrange[0]) + return; + + /* For a two-argument alloc_size, validate the product of the two + arguments if both of their values or ranges are known. */ + if (!warned && tree_fits_uhwi_p (argrange[0][0]) + && argrange[1][0] && tree_fits_uhwi_p (argrange[1][0]) + && !integer_onep (argrange[0][0]) + && !integer_onep (argrange[1][0])) + { + /* Check for overflow in the product of a function decorated with + attribute alloc_size (X, Y). */ + unsigned szprec = TYPE_PRECISION (size_type_node); + wide_int x = wi::to_wide (argrange[0][0], szprec); + wide_int y = wi::to_wide (argrange[1][0], szprec); + + bool vflow; + wide_int prod = wi::umul (x, y, &vflow); + + if (vflow) + warned = warning_at (loc, OPT_Walloc_size_larger_than_, + "product %<%E * %E%> of arguments %i and %i " + "exceeds %<SIZE_MAX%>", + argrange[0][0], argrange[1][0], + idx[0] + 1, idx[1] + 1); + else if (wi::ltu_p (wi::to_wide (maxobjsize, szprec), prod)) + warned = warning_at (loc, OPT_Walloc_size_larger_than_, + "product %<%E * %E%> of arguments %i and %i " + "exceeds maximum object size %E", + argrange[0][0], argrange[1][0], + idx[0] + 1, idx[1] + 1, + maxobjsize); + + if (warned) + { + /* Print the full range of each of the two arguments to make + it clear when it is, in fact, in a range and not constant. */ + if (argrange[0][0] != argrange [0][1]) + inform (loc, "argument %i in the range [%E, %E]", + idx[0] + 1, argrange[0][0], argrange[0][1]); + if (argrange[1][0] != argrange [1][1]) + inform (loc, "argument %i in the range [%E, %E]", + idx[1] + 1, argrange[1][0], argrange[1][1]); + } + } + + if (warned) + { + location_t fnloc = DECL_SOURCE_LOCATION (fn); + + if (DECL_IS_BUILTIN (fn)) + inform (loc, + "in a call to built-in allocation function %qD", fn); + else + inform (fnloc, + "in a call to allocation function %qD declared here", fn); + } +} + /* Issue an error if CALL_EXPR was flagged as requiring tall-call optimization. */ @@ -1359,6 +1666,24 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED, bitmap_obstack_release (NULL); + /* Extract attribute alloc_size and if set, store the indices of + the corresponding arguments in ALLOC_IDX, and then the actual + argument(s) at those indices in ALLOC_ARGS. */ + int alloc_idx[2] = { -1, -1 }; + if (tree alloc_size + = (fndecl ? lookup_attribute ("alloc_size", + TYPE_ATTRIBUTES (TREE_TYPE (fndecl))) + : NULL_TREE)) + { + tree args = TREE_VALUE (alloc_size); + alloc_idx[0] = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1; + if (TREE_CHAIN (args)) + alloc_idx[1] = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1; + } + + /* Array for up to the two attribute alloc_size arguments. */ + tree alloc_args[] = { NULL_TREE, NULL_TREE }; + /* I counts args in order (to be) pushed; ARGPOS counts in order written. */ for (argpos = 0; argpos < num_actuals; i--, argpos++) { @@ -1595,6 +1920,20 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED, targetm.calls.function_arg_advance (args_so_far, TYPE_MODE (type), type, argpos < n_named_args); + + /* Store argument values for functions decorated with attribute + alloc_size. */ + if (argpos == alloc_idx[0]) + alloc_args[0] = args[i].tree_value; + else if (argpos == alloc_idx[1]) + alloc_args[1] = args[i].tree_value; + } + + if (alloc_args[0]) + { + /* Check the arguments of functions decorated with attribute + alloc_size. */ + maybe_warn_alloc_args_overflow (fndecl, exp, alloc_args, alloc_idx); } } |