From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 1930) id 5EB9C3850432; Tue, 17 Aug 2021 20:51:35 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 5EB9C3850432 MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset="utf-8" From: Martin Sebor To: gcc-cvs@gcc.gnu.org Subject: [gcc r12-2976] Move more warning code to gimple-ssa-warn-access etc. X-Act-Checkin: gcc X-Git-Author: Martin Sebor X-Git-Refname: refs/heads/master X-Git-Oldrev: 32c3a75390623a0470df52af13f78baddd562981 X-Git-Newrev: b48d4e6818674898f90d9358378c127511ef0f9f Message-Id: <20210817205135.5EB9C3850432@sourceware.org> Date: Tue, 17 Aug 2021 20:51:35 +0000 (GMT) X-BeenThere: gcc-cvs@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-cvs mailing list List-Unsubscribe: , List-Archive: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 17 Aug 2021 20:51:35 -0000 https://gcc.gnu.org/g:b48d4e6818674898f90d9358378c127511ef0f9f commit r12-2976-gb48d4e6818674898f90d9358378c127511ef0f9f Author: Martin Sebor Date: Tue Aug 17 14:49:05 2021 -0600 Move more warning code to gimple-ssa-warn-access etc. Also resolves: PR middle-end/101854 - Invalid warning -Wstringop-overflow wrong argument gcc/ChangeLog: PR middle-end/101854 * builtins.c (expand_builtin_alloca): Move warning code to check_alloca in gimple-ssa-warn-access.cc. * calls.c (alloc_max_size): Move code to check_alloca. (get_size_range): Move to pointer-query.cc. (maybe_warn_alloc_args_overflow): Move to gimple-ssa-warn-access.cc. (get_attr_nonstring_decl): Move to tree.c. (fntype_argno_type): Move to gimple-ssa-warn-access.cc. (append_attrname): Same. (maybe_warn_rdwr_sizes): Same. (initialize_argument_information): Move code to gimple-ssa-warn-access.cc. * calls.h (maybe_warn_alloc_args_overflow): Move to gimple-ssa-warn-access.h. (get_attr_nonstring_decl): Move to tree.h. (maybe_warn_nonstring_arg): Move to gimple-ssa-warn-access.h. (enum size_range_flags): Move to pointer-query.h. (get_size_range): Same. * gimple-ssa-warn-access.cc (has_location): Remove unused overload to avoid Clang -Wunused-function. (get_size_range): Declare static. (maybe_emit_free_warning): Rename... (maybe_check_dealloc_call): ...to this for consistency. (class pass_waccess): Add members. (pass_waccess::~pass_waccess): Defined. (alloc_max_size): Move here from calls.c. (maybe_warn_alloc_args_overflow): Same. (check_alloca): New function. (check_alloc_size_call): New function. (check_strncat): Handle another warning flag. (pass_waccess::check_builtin): Handle alloca. (fntype_argno_type): Move here from calls.c. (append_attrname): Same. (maybe_warn_rdwr_sizes): Same. (pass_waccess::check_call): Define. (check_nonstring_args): New function. (pass_waccess::check): Call new member functions. (pass_waccess::execute): Enable ranger. * gimple-ssa-warn-access.h (get_size_range): Move here from calls.h. (maybe_warn_nonstring_arg): Same. * gimple-ssa-warn-restrict.c: Remove #include. * pointer-query.cc (get_size_range): Move here from calls.c. * pointer-query.h (enum size_range_flags): Same. (get_size_range): Same. * tree.c (get_attr_nonstring_decl): Move here from calls.c. * tree.h (get_attr_nonstring_decl): Move here from calls.h. gcc/testsuite/ChangeLog: * gcc.dg/attr-alloc_size-5.c: Adjust optimization to -O1. * gcc.dg/attr-alloc_size-7.c: Use #pragmas to adjust optimization. * gcc.dg/attr-alloc_size-8.c: Adjust optimization to -O1. PR middle-end/101854 * gcc.dg/Wstringop-overflow-72.c: New test. Diff: --- gcc/builtins.c | 22 +- gcc/calls.c | 760 --------------------------- gcc/calls.h | 15 +- gcc/gimple-ssa-warn-access.cc | 683 +++++++++++++++++++++++- gcc/gimple-ssa-warn-access.h | 4 +- gcc/gimple-ssa-warn-restrict.c | 1 + gcc/pointer-query.cc | 195 +++++-- gcc/pointer-query.h | 11 + gcc/testsuite/gcc.dg/Wstringop-overflow-72.c | 13 + gcc/testsuite/gcc.dg/attr-alloc_size-5.c | 2 +- gcc/testsuite/gcc.dg/attr-alloc_size-7.c | 45 +- gcc/testsuite/gcc.dg/attr-alloc_size-8.c | 2 +- gcc/tree.c | 54 ++ gcc/tree.h | 6 + 14 files changed, 952 insertions(+), 861 deletions(-) diff --git a/gcc/builtins.c b/gcc/builtins.c index d2be807f1d6..99548627761 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -43,7 +43,7 @@ along with GCC; see the file COPYING3. If not see #include "alias.h" #include "fold-const.h" #include "fold-const-call.h" -#include "gimple-ssa-warn-restrict.h" +#include "gimple-ssa-warn-access.h" #include "stor-layout.h" #include "calls.h" #include "varasm.h" @@ -81,7 +81,6 @@ along with GCC; see the file COPYING3. If not see #include "demangle.h" #include "gimple-range.h" #include "pointer-query.h" -#include "gimple-ssa-warn-access.h" struct target_builtins default_target_builtins; #if SWITCHABLE_TARGET @@ -4896,25 +4895,6 @@ expand_builtin_alloca (tree exp) if (!valid_arglist) return NULL_RTX; - if ((alloca_for_var - && warn_vla_limit >= HOST_WIDE_INT_MAX - && warn_alloc_size_limit < warn_vla_limit) - || (!alloca_for_var - && warn_alloca_limit >= HOST_WIDE_INT_MAX - && warn_alloc_size_limit < warn_alloca_limit - )) - { - /* -Walloca-larger-than and -Wvla-larger-than settings of - less than HOST_WIDE_INT_MAX override the more general - -Walloc-size-larger-than so unless either of the former - options is smaller than the last one (wchich would imply - that the call was already checked), check the alloca - arguments for overflow. */ - tree args[] = { CALL_EXPR_ARG (exp, 0), NULL_TREE }; - int idx[] = { 0, -1 }; - maybe_warn_alloc_args_overflow (fndecl, exp, args, idx); - } - /* Compute the argument. */ op0 = expand_normal (CALL_EXPR_ARG (exp, 0)); diff --git a/gcc/calls.c b/gcc/calls.c index fcb0d6dec69..e50d3fc3b62 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ -#define INCLUDE_STRING #include "config.h" #include "system.h" #include "coretypes.h" @@ -50,7 +49,6 @@ along with GCC; see the file COPYING3. If not see #include "rtl-iter.h" #include "tree-vrp.h" #include "tree-ssanames.h" -#include "tree-ssa-strlen.h" #include "intl.h" #include "stringpool.h" #include "hash-map.h" @@ -60,8 +58,6 @@ along with GCC; see the file COPYING3. If not see #include "gimple-fold.h" #include "attr-fnspec.h" #include "value-query.h" -#include "pointer-query.h" -#include "gimple-ssa-warn-access.h" #include "tree-pretty-print.h" /* Like PREFERRED_STACK_BOUNDARY but in units of bytes, not bits. */ @@ -1223,397 +1219,6 @@ 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) - return alloc_object_size_limit; - - HOST_WIDE_INT limit = warn_alloc_size_limit; - if (limit == HOST_WIDE_INT_MAX) - limit = tree_to_shwi (TYPE_MAX_VALUE (ptrdiff_type_node)); - - alloc_object_size_limit = build_int_cst (size_type_node, limit); - - return alloc_object_size_limit; -} - -/* Return true when EXP's range can be determined and set RANGE[] to it - after adjusting it if necessary to make EXP a represents a valid size - of object, or a valid size argument to an allocation function declared - with attribute alloc_size (whose argument may be signed), or to a string - manipulation function like memset. - When ALLOW_ZERO is set in FLAGS, allow returning a range of [0, 0] for - a size in an anti-range [1, N] where N > PTRDIFF_MAX. A zero range is - a (nearly) invalid argument to allocation functions like malloc but it - is a valid argument to functions like memset. - When USE_LARGEST is set in FLAGS set RANGE to the largest valid subrange - in a multi-range, otherwise to the smallest valid subrange. */ - -bool -get_size_range (range_query *query, tree exp, gimple *stmt, tree range[2], - int flags /* = 0 */) -{ - if (!exp) - return false; - - if (tree_fits_uhwi_p (exp)) - { - /* EXP is a constant. */ - range[0] = range[1] = exp; - return true; - } - - tree exptype = TREE_TYPE (exp); - bool integral = INTEGRAL_TYPE_P (exptype); - - wide_int min, max; - enum value_range_kind range_type; - - if (!query) - query = get_global_range_query (); - - if (integral) - { - value_range vr; - - query->range_of_expr (vr, exp, stmt); - - if (vr.undefined_p ()) - vr.set_varying (TREE_TYPE (exp)); - range_type = vr.kind (); - min = wi::to_wide (vr.min ()); - max = wi::to_wide (vr.max ()); - } - else - range_type = VR_VARYING; - - if (range_type == VR_VARYING) - { - if (integral) - { - /* Use the full range of the type of the expression when - no value range information is available. */ - range[0] = TYPE_MIN_VALUE (exptype); - range[1] = TYPE_MAX_VALUE (exptype); - return true; - } - - range[0] = NULL_TREE; - range[1] = NULL_TREE; - return false; - } - - unsigned expprec = TYPE_PRECISION (exptype); - - bool signed_p = !TYPE_UNSIGNED (exptype); - - if (range_type == VR_ANTI_RANGE) - { - if (signed_p) - { - if (wi::les_p (max, 0)) - { - /* EXP is not in a strictly negative range. That means - it must be in some (not necessarily strictly) positive - range which includes zero. Since in signed to unsigned - conversions negative values end up converted to large - positive values, and otherwise they are not valid sizes, - the resulting range is in both cases [0, TYPE_MAX]. */ - min = wi::zero (expprec); - max = wi::to_wide (TYPE_MAX_VALUE (exptype)); - } - else if (wi::les_p (min - 1, 0)) - { - /* EXP is not in a negative-positive range. That means EXP - is either negative, or greater than max. Since negative - sizes are invalid make the range [MAX + 1, TYPE_MAX]. */ - min = max + 1; - max = wi::to_wide (TYPE_MAX_VALUE (exptype)); - } - else - { - max = min - 1; - min = wi::zero (expprec); - } - } - else - { - wide_int maxsize = wi::to_wide (max_object_size ()); - min = wide_int::from (min, maxsize.get_precision (), UNSIGNED); - max = wide_int::from (max, maxsize.get_precision (), UNSIGNED); - if (wi::eq_p (0, min - 1)) - { - /* EXP is unsigned and not in the range [1, MAX]. That means - it's either zero or greater than MAX. Even though 0 would - normally be detected by -Walloc-zero, unless ALLOW_ZERO - is set, set the range to [MAX, TYPE_MAX] so that when MAX - is greater than the limit the whole range is diagnosed. */ - wide_int maxsize = wi::to_wide (max_object_size ()); - if (flags & SR_ALLOW_ZERO) - { - if (wi::leu_p (maxsize, max + 1) - || !(flags & SR_USE_LARGEST)) - min = max = wi::zero (expprec); - else - { - min = max + 1; - max = wi::to_wide (TYPE_MAX_VALUE (exptype)); - } - } - else - { - min = max + 1; - max = wi::to_wide (TYPE_MAX_VALUE (exptype)); - } - } - else if ((flags & SR_USE_LARGEST) - && wi::ltu_p (max + 1, maxsize)) - { - /* When USE_LARGEST is set and the larger of the two subranges - is a valid size, use it... */ - min = max + 1; - max = maxsize; - } - else - { - /* ...otherwise use the smaller subrange. */ - max = min - 1; - min = wi::zero (expprec); - } - } - } - - range[0] = wide_int_to_tree (exptype, min); - range[1] = wide_int_to_tree (exptype, max); - - return true; -} - -bool -get_size_range (tree exp, tree range[2], int flags /* = 0 */) -{ - return get_size_range (/*query=*/NULL, exp, /*stmt=*/NULL, range, flags); -} - -/* 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. FN is null when EXP is a call via a function pointer. - 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); - - tree fntype = fn ? TREE_TYPE (fn) : TREE_TYPE (TREE_TYPE (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 (fn && fndecl_built_in_p (fn, BUILT_IN_ALLOCA) - ? IDENTIFIER_LENGTH (DECL_NAME (fn)) != 6 - : !lookup_attribute ("returns_nonnull", - TYPE_ATTRIBUTES (fntype))) - 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 - && fn - && !args[1] - && lang_GNU_CXX () - && DECL_IS_OPERATOR_NEW_P (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 - && get_size_range (args[i], argrange[i])) - { - /* 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); - - wi::overflow_type 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 %", - 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 && fn) - { - location_t fnloc = DECL_SOURCE_LOCATION (fn); - - if (DECL_IS_UNDECLARED_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); - } -} - -/* If EXPR refers to a character array or pointer declared attribute - nonstring return a decl for that array or pointer and set *REF to - the referenced enclosing object or pointer. Otherwise returns - null. */ - -tree -get_attr_nonstring_decl (tree expr, tree *ref) -{ - tree decl = expr; - tree var = NULL_TREE; - if (TREE_CODE (decl) == SSA_NAME) - { - gimple *def = SSA_NAME_DEF_STMT (decl); - - if (is_gimple_assign (def)) - { - tree_code code = gimple_assign_rhs_code (def); - if (code == ADDR_EXPR - || code == COMPONENT_REF - || code == VAR_DECL) - decl = gimple_assign_rhs1 (def); - } - else - var = SSA_NAME_VAR (decl); - } - - if (TREE_CODE (decl) == ADDR_EXPR) - decl = TREE_OPERAND (decl, 0); - - /* To simplify calling code, store the referenced DECL regardless of - the attribute determined below, but avoid storing the SSA_NAME_VAR - obtained above (it's not useful for dataflow purposes). */ - if (ref) - *ref = decl; - - /* Use the SSA_NAME_VAR that was determined above to see if it's - declared nonstring. Otherwise drill down into the referenced - DECL. */ - if (var) - decl = var; - else if (TREE_CODE (decl) == ARRAY_REF) - decl = TREE_OPERAND (decl, 0); - else if (TREE_CODE (decl) == COMPONENT_REF) - decl = TREE_OPERAND (decl, 1); - else if (TREE_CODE (decl) == MEM_REF) - return get_attr_nonstring_decl (TREE_OPERAND (decl, 0), ref); - - if (DECL_P (decl) - && lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl))) - return decl; - - return NULL_TREE; -} - /* Issue an error if CALL_EXPR was flagged as requiring tall-call optimization. */ @@ -1627,310 +1232,6 @@ maybe_complain_about_tail_call (tree call_expr, const char *reason) error_at (EXPR_LOCATION (call_expr), "cannot tail-call: %s", reason); } -/* Returns the type of the argument ARGNO to function with type FNTYPE - or null when the typoe cannot be determined or no such argument exists. */ - -static tree -fntype_argno_type (tree fntype, unsigned argno) -{ - if (!prototype_p (fntype)) - return NULL_TREE; - - tree argtype; - function_args_iterator it; - FOREACH_FUNCTION_ARGS (fntype, argtype, it) - if (argno-- == 0) - return argtype; - - return NULL_TREE; -} - -/* Helper to append the "human readable" attribute access specification - described by ACCESS to the array ATTRSTR with size STRSIZE. Used in - diagnostics. */ - -static inline void -append_attrname (const std::pair &access, - char *attrstr, size_t strsize) -{ - if (access.second.internal_p) - return; - - tree str = access.second.to_external_string (); - gcc_assert (strsize >= (size_t) TREE_STRING_LENGTH (str)); - strcpy (attrstr, TREE_STRING_POINTER (str)); -} - -/* Iterate over attribute access read-only, read-write, and write-only - arguments and diagnose past-the-end accesses and related problems - in the function call EXP. */ - -static void -maybe_warn_rdwr_sizes (rdwr_map *rwm, tree fndecl, tree fntype, tree exp) -{ - auto_diagnostic_group adg; - - /* Set if a warning has been issued for any argument (used to decide - whether to emit an informational note at the end). */ - opt_code opt_warned = N_OPTS; - - /* A string describing the attributes that the warnings issued by this - function apply to. Used to print one informational note per function - call, rather than one per warning. That reduces clutter. */ - char attrstr[80]; - attrstr[0] = 0; - - for (rdwr_map::iterator it = rwm->begin (); it != rwm->end (); ++it) - { - std::pair access = *it; - - /* Get the function call arguments corresponding to the attribute's - positional arguments. When both arguments have been specified - there will be two entries in *RWM, one for each. They are - cross-referenced by their respective argument numbers in - ACCESS.PTRARG and ACCESS.SIZARG. */ - const int ptridx = access.second.ptrarg; - const int sizidx = access.second.sizarg; - - gcc_assert (ptridx != -1); - gcc_assert (access.first == ptridx || access.first == sizidx); - - /* The pointer is set to null for the entry corresponding to - the size argument. Skip it. It's handled when the entry - corresponding to the pointer argument comes up. */ - if (!access.second.ptr) - continue; - - tree ptrtype = fntype_argno_type (fntype, ptridx); - tree argtype = TREE_TYPE (ptrtype); - - /* The size of the access by the call. */ - tree access_size; - if (sizidx == -1) - { - /* If only the pointer attribute operand was specified and - not size, set SIZE to the greater of MINSIZE or size of - one element of the pointed to type to detect smaller - objects (null pointers are diagnosed in this case only - if the pointer is also declared with attribute nonnull. */ - if (access.second.minsize - && access.second.minsize != HOST_WIDE_INT_M1U) - access_size = build_int_cstu (sizetype, access.second.minsize); - else - access_size = size_one_node; - } - else - access_size = rwm->get (sizidx)->size; - - /* Format the value or range to avoid an explosion of messages. */ - char sizstr[80]; - tree sizrng[2] = { size_zero_node, build_all_ones_cst (sizetype) }; - if (get_size_range (access_size, sizrng, true)) - { - char *s0 = print_generic_expr_to_str (sizrng[0]); - if (tree_int_cst_equal (sizrng[0], sizrng[1])) - { - gcc_checking_assert (strlen (s0) < sizeof sizstr); - strcpy (sizstr, s0); - } - else - { - char *s1 = print_generic_expr_to_str (sizrng[1]); - gcc_checking_assert (strlen (s0) + strlen (s1) - < sizeof sizstr - 4); - sprintf (sizstr, "[%s, %s]", s0, s1); - free (s1); - } - free (s0); - } - else - *sizstr = '\0'; - - /* Set if a warning has been issued for the current argument. */ - opt_code arg_warned = no_warning; - location_t loc = EXPR_LOCATION (exp); - tree ptr = access.second.ptr; - if (*sizstr - && tree_int_cst_sgn (sizrng[0]) < 0 - && tree_int_cst_sgn (sizrng[1]) < 0) - { - /* Warn about negative sizes. */ - if (access.second.internal_p) - { - const std::string argtypestr - = access.second.array_as_string (ptrtype); - - if (warning_at (loc, OPT_Wstringop_overflow_, - "bound argument %i value %s is " - "negative for a variable length array " - "argument %i of type %s", - sizidx + 1, sizstr, - ptridx + 1, argtypestr.c_str ())) - arg_warned = OPT_Wstringop_overflow_; - } - else if (warning_at (loc, OPT_Wstringop_overflow_, - "argument %i value %s is negative", - sizidx + 1, sizstr)) - arg_warned = OPT_Wstringop_overflow_; - - if (arg_warned != no_warning) - { - append_attrname (access, attrstr, sizeof attrstr); - /* Remember a warning has been issued and avoid warning - again below for the same attribute. */ - opt_warned = arg_warned; - continue; - } - } - - if (tree_int_cst_sgn (sizrng[0]) >= 0) - { - if (COMPLETE_TYPE_P (argtype)) - { - /* Multiply ACCESS_SIZE by the size of the type the pointer - argument points to. If it's incomplete the size is used - as is. */ - if (tree argsize = TYPE_SIZE_UNIT (argtype)) - if (TREE_CODE (argsize) == INTEGER_CST) - { - const int prec = TYPE_PRECISION (sizetype); - wide_int minsize = wi::to_wide (sizrng[0], prec); - minsize *= wi::to_wide (argsize, prec); - access_size = wide_int_to_tree (sizetype, minsize); - } - } - } - else - access_size = NULL_TREE; - - if (integer_zerop (ptr)) - { - if (sizidx >= 0 && tree_int_cst_sgn (sizrng[0]) > 0) - { - /* Warn about null pointers with positive sizes. This is - different from also declaring the pointer argument with - attribute nonnull when the function accepts null pointers - only when the corresponding size is zero. */ - if (access.second.internal_p) - { - const std::string argtypestr - = access.second.array_as_string (ptrtype); - - if (warning_at (loc, OPT_Wnonnull, - "argument %i of variable length " - "array %s is null but " - "the corresponding bound argument " - "%i value is %s", - ptridx + 1, argtypestr.c_str (), - sizidx + 1, sizstr)) - arg_warned = OPT_Wnonnull; - } - else if (warning_at (loc, OPT_Wnonnull, - "argument %i is null but " - "the corresponding size argument " - "%i value is %s", - ptridx + 1, sizidx + 1, sizstr)) - arg_warned = OPT_Wnonnull; - } - else if (access_size && access.second.static_p) - { - /* Warn about null pointers for [static N] array arguments - but do not warn for ordinary (i.e., nonstatic) arrays. */ - if (warning_at (loc, OPT_Wnonnull, - "argument %i to %<%T[static %E]%> " - "is null where non-null expected", - ptridx + 1, argtype, access_size)) - arg_warned = OPT_Wnonnull; - } - - if (arg_warned != no_warning) - { - append_attrname (access, attrstr, sizeof attrstr); - /* Remember a warning has been issued and avoid warning - again below for the same attribute. */ - opt_warned = OPT_Wnonnull; - continue; - } - } - - access_data data (ptr, access.second.mode, NULL_TREE, false, - NULL_TREE, false); - access_ref* const pobj = (access.second.mode == access_write_only - ? &data.dst : &data.src); - tree objsize = compute_objsize (ptr, 1, pobj); - - /* The size of the destination or source object. */ - tree dstsize = NULL_TREE, srcsize = NULL_TREE; - if (access.second.mode == access_read_only - || access.second.mode == access_none) - { - /* For a read-only argument there is no destination. For - no access, set the source as well and differentiate via - the access flag below. */ - srcsize = objsize; - if (access.second.mode == access_read_only - || access.second.mode == access_none) - { - /* For a read-only attribute there is no destination so - clear OBJSIZE. This emits "reading N bytes" kind of - diagnostics instead of the "writing N bytes" kind, - unless MODE is none. */ - objsize = NULL_TREE; - } - } - else - dstsize = objsize; - - /* Clear the no-warning bit in case it was set by check_access - in a prior iteration so that accesses via different arguments - are diagnosed. */ - suppress_warning (exp, OPT_Wstringop_overflow_, false); - access_mode mode = data.mode; - if (mode == access_deferred) - mode = TYPE_READONLY (argtype) ? access_read_only : access_read_write; - check_access (exp, access_size, /*maxread=*/ NULL_TREE, srcsize, - dstsize, mode, &data); - - if (warning_suppressed_p (exp, OPT_Wstringop_overflow_)) - opt_warned = OPT_Wstringop_overflow_; - if (opt_warned != N_OPTS) - { - if (access.second.internal_p) - inform (loc, "referencing argument %u of type %qT", - ptridx + 1, ptrtype); - else - /* If check_access issued a warning above, append the relevant - attribute to the string. */ - append_attrname (access, attrstr, sizeof attrstr); - } - } - - if (*attrstr) - { - if (fndecl) - inform (DECL_SOURCE_LOCATION (fndecl), - "in a call to function %qD declared with attribute %qs", - fndecl, attrstr); - else - inform (EXPR_LOCATION (fndecl), - "in a call with type %qT and attribute %qs", - fntype, attrstr); - } - else if (opt_warned != N_OPTS) - { - if (fndecl) - inform (DECL_SOURCE_LOCATION (fndecl), - "in a call to function %qD", fndecl); - else - inform (EXPR_LOCATION (fndecl), - "in a call with type %qT", fntype); - } - - /* Set the bit in case if was cleared and not set above. */ - if (opt_warned != N_OPTS) - suppress_warning (exp, opt_warned); -} - /* Fill in ARGS_SIZE and ARGS array based on the parameters found in CALL_EXPR EXP. @@ -2030,27 +1331,6 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED, bitmap_obstack_release (NULL); - tree fntypeattrs = TYPE_ATTRIBUTES (fntype); - /* Extract attribute alloc_size from the type of the called expression - (which could be a function or a function pointer) 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 = lookup_attribute ("alloc_size", fntypeattrs)) - { - 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 }; - - /* Map of attribute accewss specifications for function arguments. */ - rdwr_map rdwr_idx; - init_attr_rdwr_indices (&rdwr_idx, fntypeattrs); - /* I counts args in order (to be) pushed; ARGPOS counts in order written. */ for (argpos = 0; argpos < num_actuals; i--, argpos++) { @@ -2283,44 +1563,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED, does pass the promoted mode. */ arg.mode = TYPE_MODE (type); targetm.calls.function_arg_advance (args_so_far, arg); - - /* 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; - - /* Save the actual argument that corresponds to the access attribute - operand for later processing. */ - if (attr_access *access = rdwr_idx.get (argpos)) - { - if (POINTER_TYPE_P (type)) - { - access->ptr = args[i].tree_value; - // A nonnull ACCESS->SIZE contains VLA bounds. */ - } - else - { - access->size = args[i].tree_value; - gcc_assert (access->ptr == NULL_TREE); - } - } - } - - 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); } - - /* Detect passing non-string arguments to functions expecting - nul-terminated strings. */ - maybe_warn_nonstring_arg (fndecl, exp); - - /* Check attribute access arguments. */ - maybe_warn_rdwr_sizes (&rdwr_idx, fndecl, fntype, exp); } /* Update ARGS_SIZE to contain the total size for the argument block. @@ -6020,6 +5263,3 @@ cxx17_empty_base_field_p (const_tree field) && RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)) && !lookup_attribute ("no_unique_address", DECL_ATTRIBUTES (field))); } - -/* Tell the garbage collector about GTY markers in this source file. */ -#include "gt-calls.h" diff --git a/gcc/calls.h b/gcc/calls.h index 4a018f6ae85..9a98f5de75f 100644 --- a/gcc/calls.h +++ b/gcc/calls.h @@ -130,21 +130,8 @@ extern bool apply_pass_by_reference_rules (CUMULATIVE_ARGS *, function_arg_info &); extern bool reference_callee_copied (CUMULATIVE_ARGS *, const function_arg_info &); -extern void maybe_warn_alloc_args_overflow (tree, tree, tree[2], int[2]); -extern tree get_attr_nonstring_decl (tree, tree * = NULL); -extern bool maybe_warn_nonstring_arg (tree, tree); extern void maybe_complain_about_tail_call (tree, const char *); -enum size_range_flags - { - /* Set to consider zero a valid range. */ - SR_ALLOW_ZERO = 1, - /* Set to use the largest subrange of a set of ranges as opposed - to the smallest. */ - SR_USE_LARGEST = 2 - }; -extern bool get_size_range (tree, tree[2], int = 0); -extern bool get_size_range (class range_query *, tree, gimple *, - tree[2], int = 0); + extern rtx rtx_for_static_chain (const_tree, bool); extern bool cxx17_empty_base_field_p (const_tree); diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc index 93f43b711e2..f3efe564af0 100644 --- a/gcc/gimple-ssa-warn-access.cc +++ b/gcc/gimple-ssa-warn-access.cc @@ -20,6 +20,7 @@ along with GCC; see the file COPYING3. If not see . */ +#define INCLUDE_STRING #include "config.h" #include "system.h" #include "coretypes.h" @@ -36,6 +37,7 @@ #include "fold-const.h" #include "gimple-fold.h" #include "gimple-iterator.h" +#include "langhooks.h" #include "tree-dfa.h" #include "tree-ssa.h" #include "tree-cfg.h" @@ -50,14 +52,6 @@ #include "demangle.h" #include "pointer-query.h" -/* Return true if STMT has an associated location. */ - -static inline location_t -has_location (const gimple *stmt) -{ - return gimple_has_location (stmt); -} - /* Return true if tree node X has an associated location. */ static inline location_t @@ -1177,7 +1171,7 @@ warn_for_access (location_t loc, tree func, tree expr, int opt, /* Helper to set RANGE to the range of BOUND if it's nonnull, bounded by BNDRNG if nonnull and valid. */ -void +static void get_size_range (tree bound, tree range[2], const offset_int bndrng[2]) { if (bound) @@ -2105,14 +2099,14 @@ warn_dealloc_offset (location_t loc, gimple *call, const access_ref &aref) form of C++ operatorn new. */ static void -maybe_emit_free_warning (gcall *call) +maybe_check_dealloc_call (gcall *call) { tree fndecl = gimple_call_fndecl (call); if (!fndecl) return; unsigned argno = fndecl_dealloc_argno (fndecl); - if ((unsigned) gimple_call_num_args (call) <= argno) + if ((unsigned) call_nargs (call) <= argno) return; tree ptr = gimple_call_arg (call, argno); @@ -2246,9 +2240,9 @@ const pass_data pass_data_waccess = { class pass_waccess : public gimple_opt_pass { public: - pass_waccess (gcc::context *ctxt) - : gimple_opt_pass (pass_data_waccess, ctxt), m_ranger () - { } + pass_waccess (gcc::context *); + + ~pass_waccess (); opt_pass *clone () { return new pass_waccess (m_ctxt); } @@ -2258,6 +2252,9 @@ class pass_waccess : public gimple_opt_pass /* Check a call to a built-in function. */ bool check_builtin (gcall *); + /* Check a call to an ordinary function. */ + bool check_call (gcall *); + /* Check statements in a basic block. */ void check (basic_block); @@ -2265,9 +2262,35 @@ class pass_waccess : public gimple_opt_pass void check (gcall *); private: + /* Not copyable or assignable. */ + pass_waccess (pass_waccess &) = delete; + void operator= (pass_waccess &) = delete; + + /* A pointer_query object and its cache to store information about + pointers and their targets in. */ + pointer_query ptr_qry; + pointer_query::cache_type var_cache; + gimple_ranger *m_ranger; }; +/* Construct the pass. */ + +pass_waccess::pass_waccess (gcc::context *ctxt) + : gimple_opt_pass (pass_data_waccess, ctxt), + ptr_qry (m_ranger, &var_cache), + var_cache (), + m_ranger () +{ +} + +/* Release pointer_query cache. */ + +pass_waccess::~pass_waccess () +{ + ptr_qry.flush_cache (); +} + /* Return true when any checks performed by the pass are enabled. */ bool @@ -2278,12 +2301,256 @@ pass_waccess::gate (function *) || warn_mismatched_new_delete); } +/* 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) +{ + HOST_WIDE_INT limit = warn_alloc_size_limit; + if (limit == HOST_WIDE_INT_MAX) + limit = tree_to_shwi (TYPE_MAX_VALUE (ptrdiff_type_node)); + + return build_int_cst (size_type_node, limit); +} + +/* 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. FN is null when EXP is a call via a function pointer. + 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 (gimple *stmt, const tree args[2], + const 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 = get_location (stmt); + + tree fn = gimple_call_fndecl (stmt); + tree fntype = fn ? TREE_TYPE (fn) : gimple_call_fntype (stmt); + 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 (fn && fndecl_built_in_p (fn, BUILT_IN_ALLOCA) + ? IDENTIFIER_LENGTH (DECL_NAME (fn)) != 6 + : !lookup_attribute ("returns_nonnull", + TYPE_ATTRIBUTES (fntype))) + 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 + && fn + && !args[1] + && lang_GNU_CXX () + && DECL_IS_OPERATOR_NEW_P (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 + && get_size_range (args[i], argrange[i])) + { + /* 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); + + wi::overflow_type 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 %", + 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 && fn) + { + location_t fnloc = DECL_SOURCE_LOCATION (fn); + + if (DECL_IS_UNDECLARED_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); + } +} + +/* Check a call to an alloca function for an excessive size. */ + +static void +check_alloca (gimple *stmt) +{ + if ((warn_vla_limit >= HOST_WIDE_INT_MAX + && warn_alloc_size_limit < warn_vla_limit) + || (warn_alloca_limit >= HOST_WIDE_INT_MAX + && warn_alloc_size_limit < warn_alloca_limit)) + { + /* -Walloca-larger-than and -Wvla-larger-than settings of less + than HWI_MAX override the more general -Walloc-size-larger-than + so unless either of the former options is smaller than the last + one (wchich would imply that the call was already checked), check + the alloca arguments for overflow. */ + const tree alloc_args[] = { call_arg (stmt, 0), NULL_TREE }; + const int idx[] = { 0, -1 }; + maybe_warn_alloc_args_overflow (stmt, alloc_args, idx); + } +} + +/* Check a call to an allocation function for an excessive size. */ + +static void +check_alloc_size_call (gimple *stmt) +{ + if (gimple_call_num_args (stmt) < 1) + /* Avoid invalid calls to functions without a prototype. */ + return; + + tree fndecl = gimple_call_fndecl (stmt); + if (fndecl && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) + { + /* Alloca is handled separately. */ + switch (DECL_FUNCTION_CODE (fndecl)) + { + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: + case BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX: + return; + default: + break; + } + } + + tree fntype = gimple_call_fntype (stmt); + tree fntypeattrs = TYPE_ATTRIBUTES (fntype); + + tree alloc_size = lookup_attribute ("alloc_size", fntypeattrs); + if (!alloc_size) + return; + + /* Extract attribute alloc_size from the type of the called expression + (which could be a function or a function pointer) 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 idx[2] = { -1, -1 }; + tree alloc_args[] = { NULL_TREE, NULL_TREE }; + + tree args = TREE_VALUE (alloc_size); + idx[0] = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1; + alloc_args[0] = call_arg (stmt, idx[0]); + if (TREE_CHAIN (args)) + { + idx[1] = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1; + alloc_args[1] = call_arg (stmt, idx[1]); + } + + maybe_warn_alloc_args_overflow (stmt, alloc_args, idx); +} + /* Check a call STMT to strcat() for overflow and warn if it does. */ static void check_strcat (gimple *stmt) { - if (!warn_stringop_overflow) + if (!warn_stringop_overflow && !warn_stringop_overread) return; tree dest = call_arg (stmt, 0); @@ -2308,7 +2575,7 @@ check_strcat (gimple *stmt) static void check_strncat (gimple *stmt) { - if (!warn_stringop_overflow) + if (!warn_stringop_overflow && !warn_stringop_overread) return; tree dest = call_arg (stmt, 0); @@ -2537,6 +2804,12 @@ pass_waccess::check_builtin (gcall *stmt) switch (DECL_FUNCTION_CODE (callee)) { + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: + case BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX: + check_alloca (stmt); + return true; + case BUILT_IN_GETTEXT: case BUILT_IN_PUTS: case BUILT_IN_PUTS_UNLOCKED: @@ -2639,16 +2912,384 @@ pass_waccess::check_builtin (gcall *stmt) return true; } +/* Returns the type of the argument ARGNO to function with type FNTYPE + or null when the typoe cannot be determined or no such argument exists. */ + +static tree +fntype_argno_type (tree fntype, unsigned argno) +{ + if (!prototype_p (fntype)) + return NULL_TREE; + + tree argtype; + function_args_iterator it; + FOREACH_FUNCTION_ARGS (fntype, argtype, it) + if (argno-- == 0) + return argtype; + + return NULL_TREE; +} + +/* Helper to append the "human readable" attribute access specification + described by ACCESS to the array ATTRSTR with size STRSIZE. Used in + diagnostics. */ + +static inline void +append_attrname (const std::pair &access, + char *attrstr, size_t strsize) +{ + if (access.second.internal_p) + return; + + tree str = access.second.to_external_string (); + gcc_assert (strsize >= (size_t) TREE_STRING_LENGTH (str)); + strcpy (attrstr, TREE_STRING_POINTER (str)); +} + +/* Iterate over attribute access read-only, read-write, and write-only + arguments and diagnose past-the-end accesses and related problems + in the function call EXP. */ + +static void +maybe_warn_rdwr_sizes (rdwr_map *rwm, tree fndecl, tree fntype, gimple *stmt) +{ + auto_diagnostic_group adg; + + /* Set if a warning has been issued for any argument (used to decide + whether to emit an informational note at the end). */ + opt_code opt_warned = no_warning; + + /* A string describing the attributes that the warnings issued by this + function apply to. Used to print one informational note per function + call, rather than one per warning. That reduces clutter. */ + char attrstr[80]; + attrstr[0] = 0; + + for (rdwr_map::iterator it = rwm->begin (); it != rwm->end (); ++it) + { + std::pair access = *it; + + /* Get the function call arguments corresponding to the attribute's + positional arguments. When both arguments have been specified + there will be two entries in *RWM, one for each. They are + cross-referenced by their respective argument numbers in + ACCESS.PTRARG and ACCESS.SIZARG. */ + const int ptridx = access.second.ptrarg; + const int sizidx = access.second.sizarg; + + gcc_assert (ptridx != -1); + gcc_assert (access.first == ptridx || access.first == sizidx); + + /* The pointer is set to null for the entry corresponding to + the size argument. Skip it. It's handled when the entry + corresponding to the pointer argument comes up. */ + if (!access.second.ptr) + continue; + + tree ptrtype = fntype_argno_type (fntype, ptridx); + tree argtype = TREE_TYPE (ptrtype); + + /* The size of the access by the call. */ + tree access_size; + if (sizidx == -1) + { + /* If only the pointer attribute operand was specified and + not size, set SIZE to the greater of MINSIZE or size of + one element of the pointed to type to detect smaller + objects (null pointers are diagnosed in this case only + if the pointer is also declared with attribute nonnull. */ + if (access.second.minsize + && access.second.minsize != HOST_WIDE_INT_M1U) + access_size = build_int_cstu (sizetype, access.second.minsize); + else + access_size = size_one_node; + } + else + access_size = rwm->get (sizidx)->size; + + /* Format the value or range to avoid an explosion of messages. */ + char sizstr[80]; + tree sizrng[2] = { size_zero_node, build_all_ones_cst (sizetype) }; + if (get_size_range (access_size, sizrng, true)) + { + char *s0 = print_generic_expr_to_str (sizrng[0]); + if (tree_int_cst_equal (sizrng[0], sizrng[1])) + { + gcc_checking_assert (strlen (s0) < sizeof sizstr); + strcpy (sizstr, s0); + } + else + { + char *s1 = print_generic_expr_to_str (sizrng[1]); + gcc_checking_assert (strlen (s0) + strlen (s1) + < sizeof sizstr - 4); + sprintf (sizstr, "[%s, %s]", s0, s1); + free (s1); + } + free (s0); + } + else + *sizstr = '\0'; + + /* Set if a warning has been issued for the current argument. */ + opt_code arg_warned = no_warning; + location_t loc = get_location (stmt); + tree ptr = access.second.ptr; + if (*sizstr + && tree_int_cst_sgn (sizrng[0]) < 0 + && tree_int_cst_sgn (sizrng[1]) < 0) + { + /* Warn about negative sizes. */ + if (access.second.internal_p) + { + const std::string argtypestr + = access.second.array_as_string (ptrtype); + + if (warning_at (loc, OPT_Wstringop_overflow_, + "bound argument %i value %s is " + "negative for a variable length array " + "argument %i of type %s", + sizidx + 1, sizstr, + ptridx + 1, argtypestr.c_str ())) + arg_warned = OPT_Wstringop_overflow_; + } + else if (warning_at (loc, OPT_Wstringop_overflow_, + "argument %i value %s is negative", + sizidx + 1, sizstr)) + arg_warned = OPT_Wstringop_overflow_; + + if (arg_warned != no_warning) + { + append_attrname (access, attrstr, sizeof attrstr); + /* Remember a warning has been issued and avoid warning + again below for the same attribute. */ + opt_warned = arg_warned; + continue; + } + } + + if (tree_int_cst_sgn (sizrng[0]) >= 0) + { + if (COMPLETE_TYPE_P (argtype)) + { + /* Multiply ACCESS_SIZE by the size of the type the pointer + argument points to. If it's incomplete the size is used + as is. */ + if (tree argsize = TYPE_SIZE_UNIT (argtype)) + if (TREE_CODE (argsize) == INTEGER_CST) + { + const int prec = TYPE_PRECISION (sizetype); + wide_int minsize = wi::to_wide (sizrng[0], prec); + minsize *= wi::to_wide (argsize, prec); + access_size = wide_int_to_tree (sizetype, minsize); + } + } + } + else + access_size = NULL_TREE; + + if (integer_zerop (ptr)) + { + if (sizidx >= 0 && tree_int_cst_sgn (sizrng[0]) > 0) + { + /* Warn about null pointers with positive sizes. This is + different from also declaring the pointer argument with + attribute nonnull when the function accepts null pointers + only when the corresponding size is zero. */ + if (access.second.internal_p) + { + const std::string argtypestr + = access.second.array_as_string (ptrtype); + + if (warning_at (loc, OPT_Wnonnull, + "argument %i of variable length " + "array %s is null but " + "the corresponding bound argument " + "%i value is %s", + ptridx + 1, argtypestr.c_str (), + sizidx + 1, sizstr)) + arg_warned = OPT_Wnonnull; + } + else if (warning_at (loc, OPT_Wnonnull, + "argument %i is null but " + "the corresponding size argument " + "%i value is %s", + ptridx + 1, sizidx + 1, sizstr)) + arg_warned = OPT_Wnonnull; + } + else if (access_size && access.second.static_p) + { + /* Warn about null pointers for [static N] array arguments + but do not warn for ordinary (i.e., nonstatic) arrays. */ + if (warning_at (loc, OPT_Wnonnull, + "argument %i to %<%T[static %E]%> " + "is null where non-null expected", + ptridx + 1, argtype, access_size)) + arg_warned = OPT_Wnonnull; + } + + if (arg_warned != no_warning) + { + append_attrname (access, attrstr, sizeof attrstr); + /* Remember a warning has been issued and avoid warning + again below for the same attribute. */ + opt_warned = OPT_Wnonnull; + continue; + } + } + + access_data data (ptr, access.second.mode, NULL_TREE, false, + NULL_TREE, false); + access_ref* const pobj = (access.second.mode == access_write_only + ? &data.dst : &data.src); + tree objsize = compute_objsize (ptr, 1, pobj); + + /* The size of the destination or source object. */ + tree dstsize = NULL_TREE, srcsize = NULL_TREE; + if (access.second.mode == access_read_only + || access.second.mode == access_none) + { + /* For a read-only argument there is no destination. For + no access, set the source as well and differentiate via + the access flag below. */ + srcsize = objsize; + if (access.second.mode == access_read_only + || access.second.mode == access_none) + { + /* For a read-only attribute there is no destination so + clear OBJSIZE. This emits "reading N bytes" kind of + diagnostics instead of the "writing N bytes" kind, + unless MODE is none. */ + objsize = NULL_TREE; + } + } + else + dstsize = objsize; + + /* Clear the no-warning bit in case it was set by check_access + in a prior iteration so that accesses via different arguments + are diagnosed. */ + suppress_warning (stmt, OPT_Wstringop_overflow_, false); + access_mode mode = data.mode; + if (mode == access_deferred) + mode = TYPE_READONLY (argtype) ? access_read_only : access_read_write; + check_access (stmt, access_size, /*maxread=*/ NULL_TREE, srcsize, + dstsize, mode, &data); + + if (warning_suppressed_p (stmt, OPT_Wstringop_overflow_)) + opt_warned = OPT_Wstringop_overflow_; + if (opt_warned != no_warning) + { + if (access.second.internal_p) + inform (loc, "referencing argument %u of type %qT", + ptridx + 1, ptrtype); + else + /* If check_access issued a warning above, append the relevant + attribute to the string. */ + append_attrname (access, attrstr, sizeof attrstr); + } + } + + if (*attrstr) + { + if (fndecl) + inform (get_location (fndecl), + "in a call to function %qD declared with attribute %qs", + fndecl, attrstr); + else + inform (get_location (stmt), + "in a call with type %qT and attribute %qs", + fntype, attrstr); + } + else if (opt_warned != no_warning) + { + if (fndecl) + inform (get_location (fndecl), + "in a call to function %qD", fndecl); + else + inform (get_location (stmt), + "in a call with type %qT", fntype); + } + + /* Set the bit in case if was cleared and not set above. */ + if (opt_warned != no_warning) + suppress_warning (stmt, opt_warned); +} + +/* Check call STMT to an ordinary (non-built-in) function for invalid + accesses. Return true if a call has been handled. */ + +bool +pass_waccess::check_call (gcall *stmt) +{ + tree fntype = gimple_call_fntype (stmt); + if (!fntype) + return false; + + tree fntypeattrs = TYPE_ATTRIBUTES (fntype); + if (!fntypeattrs) + return false; + + /* Map of attribute accewss specifications for function arguments. */ + rdwr_map rdwr_idx; + init_attr_rdwr_indices (&rdwr_idx, fntypeattrs); + + unsigned nargs = call_nargs (stmt); + for (unsigned i = 0; i != nargs; ++i) + { + tree arg = call_arg (stmt, i); + + /* Save the actual argument that corresponds to the access attribute + operand for later processing. */ + if (attr_access *access = rdwr_idx.get (i)) + { + if (POINTER_TYPE_P (TREE_TYPE (arg))) + { + access->ptr = arg; + // A nonnull ACCESS->SIZE contains VLA bounds. */ + } + else + { + access->size = arg; + gcc_assert (access->ptr == NULL_TREE); + } + } + } + + /* Check attribute access arguments. */ + tree fndecl = gimple_call_fndecl (stmt); + maybe_warn_rdwr_sizes (&rdwr_idx, fndecl, fntype, stmt); + + check_alloc_size_call (stmt); + return true; +} + +/* Check arguments in a call STMT for attribute nonstring. */ + +static void +check_nonstring_args (gcall *stmt) +{ + tree fndecl = gimple_call_fndecl (stmt); + + /* Detect passing non-string arguments to functions expecting + nul-terminated strings. */ + maybe_warn_nonstring_arg (fndecl, stmt); +} + /* Check call STMT for invalid accesses. */ void pass_waccess::check (gcall *stmt) { - if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL) - && check_builtin (stmt)) - return; + if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) + check_builtin (stmt); - maybe_emit_free_warning (stmt); + if (is_gimple_call (stmt)) + check_call (stmt); + + maybe_check_dealloc_call (stmt); + + check_nonstring_args (stmt); } /* Check basic block BB for invalid accesses. */ @@ -2669,6 +3310,8 @@ pass_waccess::check (basic_block bb) unsigned pass_waccess::execute (function *fun) { + m_ranger = enable_ranger (fun); + basic_block bb; FOR_EACH_BB_FN (bb, fun) check (bb); diff --git a/gcc/gimple-ssa-warn-access.h b/gcc/gimple-ssa-warn-access.h index 8b33ecbd4fb..1cd3a28c421 100644 --- a/gcc/gimple-ssa-warn-access.h +++ b/gcc/gimple-ssa-warn-access.h @@ -31,7 +31,9 @@ extern void warn_string_no_nul (location_t, tree, const char *, tree, tree, tree = NULL_TREE, bool = false, const wide_int[2] = NULL); extern tree unterminated_array (tree, tree * = NULL, bool * = NULL); -extern void get_size_range (tree, tree[2], const offset_int[2]); + +extern bool maybe_warn_nonstring_arg (tree, gimple *); +extern bool maybe_warn_nonstring_arg (tree, tree); class access_data; extern bool maybe_warn_for_bound (opt_code, location_t, gimple *, tree, diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c index 404acb03195..d1df9ca8d4f 100644 --- a/gcc/gimple-ssa-warn-restrict.c +++ b/gcc/gimple-ssa-warn-restrict.c @@ -29,6 +29,7 @@ #include "pointer-query.h" #include "ssa.h" #include "gimple-pretty-print.h" +#include "gimple-ssa-warn-access.h" #include "gimple-ssa-warn-restrict.h" #include "diagnostic-core.h" #include "fold-const.h" diff --git a/gcc/pointer-query.cc b/gcc/pointer-query.cc index bc7cac092f5..99caf78bfa7 100644 --- a/gcc/pointer-query.cc +++ b/gcc/pointer-query.cc @@ -22,58 +22,21 @@ #include "system.h" #include "coretypes.h" #include "backend.h" -#include "target.h" -#include "rtl.h" #include "tree.h" -#include "memmodel.h" #include "gimple.h" -#include "predict.h" -#include "tm_p.h" #include "stringpool.h" #include "tree-vrp.h" -#include "tree-ssanames.h" -#include "expmed.h" -#include "optabs.h" -#include "emit-rtl.h" -#include "recog.h" #include "diagnostic-core.h" -#include "alias.h" #include "fold-const.h" -#include "fold-const-call.h" -#include "gimple-ssa-warn-restrict.h" -#include "stor-layout.h" -#include "calls.h" -#include "varasm.h" #include "tree-object-size.h" #include "tree-ssa-strlen.h" -#include "realmpfr.h" -#include "cfgrtl.h" -#include "except.h" -#include "dojump.h" -#include "explow.h" -#include "stmt.h" -#include "expr.h" -#include "libfuncs.h" -#include "output.h" -#include "typeclass.h" #include "langhooks.h" -#include "value-prof.h" -#include "builtins.h" #include "stringpool.h" #include "attribs.h" -#include "asan.h" -#include "internal-fn.h" -#include "case-cfn-macros.h" #include "gimple-fold.h" #include "intl.h" -#include "tree-dfa.h" -#include "gimple-iterator.h" -#include "gimple-ssa.h" -#include "tree-ssa-live.h" -#include "tree-outof-ssa.h" #include "attr-fnspec.h" #include "gimple-range.h" - #include "pointer-query.h" static bool compute_objsize_r (tree, int, access_ref *, ssa_name_limit_t &, @@ -311,6 +274,164 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end, return NULL_TREE; } +/* Return true when EXP's range can be determined and set RANGE[] to it + after adjusting it if necessary to make EXP a represents a valid size + of object, or a valid size argument to an allocation function declared + with attribute alloc_size (whose argument may be signed), or to a string + manipulation function like memset. + When ALLOW_ZERO is set in FLAGS, allow returning a range of [0, 0] for + a size in an anti-range [1, N] where N > PTRDIFF_MAX. A zero range is + a (nearly) invalid argument to allocation functions like malloc but it + is a valid argument to functions like memset. + When USE_LARGEST is set in FLAGS set RANGE to the largest valid subrange + in a multi-range, otherwise to the smallest valid subrange. */ + +bool +get_size_range (range_query *query, tree exp, gimple *stmt, tree range[2], + int flags /* = 0 */) +{ + if (!exp) + return false; + + if (tree_fits_uhwi_p (exp)) + { + /* EXP is a constant. */ + range[0] = range[1] = exp; + return true; + } + + tree exptype = TREE_TYPE (exp); + bool integral = INTEGRAL_TYPE_P (exptype); + + wide_int min, max; + enum value_range_kind range_type; + + if (!query) + query = get_global_range_query (); + + if (integral) + { + value_range vr; + + query->range_of_expr (vr, exp, stmt); + + if (vr.undefined_p ()) + vr.set_varying (TREE_TYPE (exp)); + range_type = vr.kind (); + min = wi::to_wide (vr.min ()); + max = wi::to_wide (vr.max ()); + } + else + range_type = VR_VARYING; + + if (range_type == VR_VARYING) + { + if (integral) + { + /* Use the full range of the type of the expression when + no value range information is available. */ + range[0] = TYPE_MIN_VALUE (exptype); + range[1] = TYPE_MAX_VALUE (exptype); + return true; + } + + range[0] = NULL_TREE; + range[1] = NULL_TREE; + return false; + } + + unsigned expprec = TYPE_PRECISION (exptype); + + bool signed_p = !TYPE_UNSIGNED (exptype); + + if (range_type == VR_ANTI_RANGE) + { + if (signed_p) + { + if (wi::les_p (max, 0)) + { + /* EXP is not in a strictly negative range. That means + it must be in some (not necessarily strictly) positive + range which includes zero. Since in signed to unsigned + conversions negative values end up converted to large + positive values, and otherwise they are not valid sizes, + the resulting range is in both cases [0, TYPE_MAX]. */ + min = wi::zero (expprec); + max = wi::to_wide (TYPE_MAX_VALUE (exptype)); + } + else if (wi::les_p (min - 1, 0)) + { + /* EXP is not in a negative-positive range. That means EXP + is either negative, or greater than max. Since negative + sizes are invalid make the range [MAX + 1, TYPE_MAX]. */ + min = max + 1; + max = wi::to_wide (TYPE_MAX_VALUE (exptype)); + } + else + { + max = min - 1; + min = wi::zero (expprec); + } + } + else + { + wide_int maxsize = wi::to_wide (max_object_size ()); + min = wide_int::from (min, maxsize.get_precision (), UNSIGNED); + max = wide_int::from (max, maxsize.get_precision (), UNSIGNED); + if (wi::eq_p (0, min - 1)) + { + /* EXP is unsigned and not in the range [1, MAX]. That means + it's either zero or greater than MAX. Even though 0 would + normally be detected by -Walloc-zero, unless ALLOW_ZERO + is set, set the range to [MAX, TYPE_MAX] so that when MAX + is greater than the limit the whole range is diagnosed. */ + wide_int maxsize = wi::to_wide (max_object_size ()); + if (flags & SR_ALLOW_ZERO) + { + if (wi::leu_p (maxsize, max + 1) + || !(flags & SR_USE_LARGEST)) + min = max = wi::zero (expprec); + else + { + min = max + 1; + max = wi::to_wide (TYPE_MAX_VALUE (exptype)); + } + } + else + { + min = max + 1; + max = wi::to_wide (TYPE_MAX_VALUE (exptype)); + } + } + else if ((flags & SR_USE_LARGEST) + && wi::ltu_p (max + 1, maxsize)) + { + /* When USE_LARGEST is set and the larger of the two subranges + is a valid size, use it... */ + min = max + 1; + max = maxsize; + } + else + { + /* ...otherwise use the smaller subrange. */ + max = min - 1; + min = wi::zero (expprec); + } + } + } + + range[0] = wide_int_to_tree (exptype, min); + range[1] = wide_int_to_tree (exptype, max); + + return true; +} + +bool +get_size_range (tree exp, tree range[2], int flags /* = 0 */) +{ + return get_size_range (/*query=*/NULL, exp, /*stmt=*/NULL, range, flags); +} + /* If STMT is a call to an allocation function, returns the constant maximum size of the object allocated by the call represented as sizetype. If nonnull, sets RNG1[] to the range of the size. diff --git a/gcc/pointer-query.h b/gcc/pointer-query.h index 8bd538a3ac2..eb7e90dde03 100644 --- a/gcc/pointer-query.h +++ b/gcc/pointer-query.h @@ -230,6 +230,17 @@ struct access_data access_mode mode; }; +enum size_range_flags + { + /* Set to consider zero a valid range. */ + SR_ALLOW_ZERO = 1, + /* Set to use the largest subrange of a set of ranges as opposed + to the smallest. */ + SR_USE_LARGEST = 2 + }; +extern bool get_size_range (tree, tree[2], int = 0); +extern bool get_size_range (range_query *, tree, gimple *, tree[2], int = 0); + class range_query; extern tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL, range_query * = NULL); diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-72.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-72.c new file mode 100644 index 00000000000..c10773e90a3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-72.c @@ -0,0 +1,13 @@ +/* PR middle-end/101854 - Invalid warning -Wstringop-overflow wrong argument + { dg-do compile } + { dg-options "-O2 -Wall" } */ + +struct A { int a[5]; }; + +struct A g (int*, int[6][8]); + +struct A f (void) +{ + int a[2]; + return g (a, 0); // { dg-bogus "-Wstringop-overflow" } +} diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-5.c b/gcc/testsuite/gcc.dg/attr-alloc_size-5.c index 7aa7cbf0c72..4eea625a966 100644 --- a/gcc/testsuite/gcc.dg/attr-alloc_size-5.c +++ b/gcc/testsuite/gcc.dg/attr-alloc_size-5.c @@ -4,7 +4,7 @@ zero bytes. For standard allocation functions the return value is implementation-defined and so relying on it may be a source of bugs. */ /* { dg-do compile } */ -/* { dg-options "-O2 -Wall -Walloc-zero" } */ +/* { dg-options "-O1 -Wall -Walloc-zero" } */ #define SCHAR_MAX __SCHAR_MAX__ #define SCHAR_MIN (-SCHAR_MAX - 1) diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-7.c b/gcc/testsuite/gcc.dg/attr-alloc_size-7.c index 68602ec37d9..3adde5c2270 100644 --- a/gcc/testsuite/gcc.dg/attr-alloc_size-7.c +++ b/gcc/testsuite/gcc.dg/attr-alloc_size-7.c @@ -4,7 +4,7 @@ of the maximum specified by -Walloc-size-larger-than=maximum. */ /* { dg-do compile } */ /* { dg-require-effective-target alloca } */ -/* { dg-options "-O2 -Wall -Walloc-size-larger-than=12345" } */ +/* { dg-options "-O1 -Wall -Walloc-size-larger-than=12345" } */ #define SIZE_MAX __SIZE_MAX__ #define MAXOBJSZ 12345 @@ -13,15 +13,40 @@ typedef __SIZE_TYPE__ size_t; void sink (void*); -static size_t maxobjsize (void) +#pragma GCC push_options +/* Verify that constant evaluation takes place even at -O0. */ +#pragma GCC optimize ("0") + +void test_cst (void *p) { - return MAXOBJSZ; + enum { max = MAXOBJSZ }; + + sink (__builtin_aligned_alloc (1, max)); + sink (__builtin_aligned_alloc (1, max + 1)); /* { dg-warning "argument 2 value .12346\[lu\]*. exceeds maximum object size 12345" } */ + + sink (__builtin_alloca (max)); + sink (__builtin_alloca (max + 2)); /* { dg-warning "argument 1 value .12347\[lu\]*. exceeds maximum object size 12345" } */ + + sink (__builtin_calloc (1, max)); + sink (__builtin_calloc (max, 1)); + + sink (__builtin_calloc (max / 2, 3)); /* { dg-warning "product .6172\[lu\]* \\* 3\[lu\]*. of arguments 1 and 2 exceeds maximum object size 12345" } */ + sink (__builtin_calloc (4, max / 3)); /* { dg-warning "product .4\[lu\]* \\* 4115\[lu\]*. of arguments 1 and 2 exceeds maximum object size 12345" } */ + + sink (__builtin_malloc (max)); + sink (__builtin_malloc (max + 3)); /* { dg-warning "argument 1 value .12348\[lu\]*. exceeds maximum object size 12345" } */ + + sink (__builtin_realloc (p, max)); + sink (__builtin_realloc (p, max + 4)); /* { dg-warning "argument 2 value .12349\[lu\]*. exceeds maximum object size 12345" } */ } -void test_var (void *p) +/* Variable evaluation needs -O1. */ +#pragma GCC pop_options + +__attribute__ ((noipa)) void test_var (void *p) { - size_t max = maxobjsize (); + size_t max = MAXOBJSZ; sink (__builtin_aligned_alloc (1, max)); sink (__builtin_aligned_alloc (1, max + 1)); /* { dg-warning "argument 2 value .12346\[lu\]*. exceeds maximum object size 12345" } */ @@ -43,7 +68,15 @@ void test_var (void *p) } -void test_range (void *p, size_t range) +/* Value range evaluation (apparently) needs -O2 here. */ +#pragma GCC optimize ("2") + +static size_t maxobjsize (void) +{ + return MAXOBJSZ; +} + +__attribute__ ((noipa)) void test_range (void *p, size_t range) { /* Make sure the variable is at least as large as the maximum object size but also make sure that it's guaranteed not to be too big to diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-8.c b/gcc/testsuite/gcc.dg/attr-alloc_size-8.c index 91d7eb58e29..7b47b045b11 100644 --- a/gcc/testsuite/gcc.dg/attr-alloc_size-8.c +++ b/gcc/testsuite/gcc.dg/attr-alloc_size-8.c @@ -4,7 +4,7 @@ two more specific options override the more general latter option. */ /* { dg-do compile } */ /* { dg-require-effective-target alloca } */ -/* { dg-options "-O2 -Walloc-size-larger-than=123 -Walloca-larger-than=234 -Wvla-larger-than=345" } */ +/* { dg-options "-O -Walloc-size-larger-than=123 -Walloca-larger-than=234 -Wvla-larger-than=345" } */ typedef __SIZE_TYPE__ size_t; diff --git a/gcc/tree.c b/gcc/tree.c index 6ec8a9738c8..cba3bca41b3 100644 --- a/gcc/tree.c +++ b/gcc/tree.c @@ -14464,6 +14464,60 @@ fndecl_dealloc_argno (tree fndecl) return UINT_MAX; } +/* If EXPR refers to a character array or pointer declared attribute + nonstring, return a decl for that array or pointer and set *REF + to the referenced enclosing object or pointer. Otherwise return + null. */ + +tree +get_attr_nonstring_decl (tree expr, tree *ref) +{ + tree decl = expr; + tree var = NULL_TREE; + if (TREE_CODE (decl) == SSA_NAME) + { + gimple *def = SSA_NAME_DEF_STMT (decl); + + if (is_gimple_assign (def)) + { + tree_code code = gimple_assign_rhs_code (def); + if (code == ADDR_EXPR + || code == COMPONENT_REF + || code == VAR_DECL) + decl = gimple_assign_rhs1 (def); + } + else + var = SSA_NAME_VAR (decl); + } + + if (TREE_CODE (decl) == ADDR_EXPR) + decl = TREE_OPERAND (decl, 0); + + /* To simplify calling code, store the referenced DECL regardless of + the attribute determined below, but avoid storing the SSA_NAME_VAR + obtained above (it's not useful for dataflow purposes). */ + if (ref) + *ref = decl; + + /* Use the SSA_NAME_VAR that was determined above to see if it's + declared nonstring. Otherwise drill down into the referenced + DECL. */ + if (var) + decl = var; + else if (TREE_CODE (decl) == ARRAY_REF) + decl = TREE_OPERAND (decl, 0); + else if (TREE_CODE (decl) == COMPONENT_REF) + decl = TREE_OPERAND (decl, 1); + else if (TREE_CODE (decl) == MEM_REF) + return get_attr_nonstring_decl (TREE_OPERAND (decl, 0), ref); + + if (DECL_P (decl) + && lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl))) + return decl; + + return NULL_TREE; +} + #if CHECKING_P namespace selftest { diff --git a/gcc/tree.h b/gcc/tree.h index 78d8a049d29..905417fd17b 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -6488,4 +6488,10 @@ extern void copy_warning (tree, const_tree); value if it isn't. */ extern unsigned fndecl_dealloc_argno (tree); +/* If an expression refers to a character array or pointer declared + attribute nonstring, return a decl for that array or pointer and + if nonnull, set the second argument to the referenced enclosing + object or pointer. Otherwise return null. */ +extern tree get_attr_nonstring_decl (tree, tree * = NULL); + #endif /* GCC_TREE_H */