diff options
author | Ian Lance Taylor <iant@golang.org> | 2021-09-13 10:37:49 -0700 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2021-09-13 10:37:49 -0700 |
commit | e252b51ccde010cbd2a146485d8045103cd99533 (patch) | |
tree | e060f101cdc32bf5e520de8e5275db9d4236b74c /gcc/pointer-query.cc | |
parent | f10c7c4596dda99d2ee872c995ae4aeda65adbdf (diff) | |
parent | 104c05c5284b7822d770ee51a7d91946c7e56d50 (diff) | |
download | gcc-e252b51ccde010cbd2a146485d8045103cd99533.zip gcc-e252b51ccde010cbd2a146485d8045103cd99533.tar.gz gcc-e252b51ccde010cbd2a146485d8045103cd99533.tar.bz2 |
Merge from trunk revision 104c05c5284b7822d770ee51a7d91946c7e56d50.
Diffstat (limited to 'gcc/pointer-query.cc')
-rw-r--r-- | gcc/pointer-query.cc | 2142 |
1 files changed, 2142 insertions, 0 deletions
diff --git a/gcc/pointer-query.cc b/gcc/pointer-query.cc new file mode 100644 index 0000000..4ad2879 --- /dev/null +++ b/gcc/pointer-query.cc @@ -0,0 +1,2142 @@ +/* Definitions of the pointer_query and related classes. + + Copyright (C) 2020-2021 Free Software Foundation, Inc. + + This file is part of GCC. + + GCC is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 3, or (at your option) any later + version. + + GCC is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with GCC; see the file COPYING3. If not see + <http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "tree.h" +#include "gimple.h" +#include "stringpool.h" +#include "tree-vrp.h" +#include "diagnostic-core.h" +#include "fold-const.h" +#include "tree-object-size.h" +#include "tree-ssa-strlen.h" +#include "langhooks.h" +#include "stringpool.h" +#include "attribs.h" +#include "gimple-fold.h" +#include "gimple-ssa.h" +#include "intl.h" +#include "attr-fnspec.h" +#include "gimple-range.h" +#include "pointer-query.h" +#include "tree-pretty-print.h" +#include "tree-ssanames.h" + +static bool compute_objsize_r (tree, int, access_ref *, ssa_name_limit_t &, + pointer_query *); + +/* Wrapper around the wide_int overload of get_range that accepts + offset_int instead. For middle end expressions returns the same + result. For a subset of nonconstamt expressions emitted by the front + end determines a more precise range than would be possible otherwise. */ + +static bool +get_offset_range (tree x, gimple *stmt, offset_int r[2], range_query *rvals) +{ + offset_int add = 0; + if (TREE_CODE (x) == PLUS_EXPR) + { + /* Handle constant offsets in pointer addition expressions seen + n the front end IL. */ + tree op = TREE_OPERAND (x, 1); + if (TREE_CODE (op) == INTEGER_CST) + { + op = fold_convert (signed_type_for (TREE_TYPE (op)), op); + add = wi::to_offset (op); + x = TREE_OPERAND (x, 0); + } + } + + if (TREE_CODE (x) == NOP_EXPR) + /* Also handle conversions to sizetype seen in the front end IL. */ + x = TREE_OPERAND (x, 0); + + tree type = TREE_TYPE (x); + if (!INTEGRAL_TYPE_P (type) && !POINTER_TYPE_P (type)) + return false; + + if (TREE_CODE (x) != INTEGER_CST + && TREE_CODE (x) != SSA_NAME) + { + if (TYPE_UNSIGNED (type) + && TYPE_PRECISION (type) == TYPE_PRECISION (sizetype)) + type = signed_type_for (type); + + r[0] = wi::to_offset (TYPE_MIN_VALUE (type)) + add; + r[1] = wi::to_offset (TYPE_MAX_VALUE (type)) + add; + return x; + } + + wide_int wr[2]; + if (!get_range (x, stmt, wr, rvals)) + return false; + + signop sgn = SIGNED; + /* Only convert signed integers or unsigned sizetype to a signed + offset and avoid converting large positive values in narrower + types to negative offsets. */ + if (TYPE_UNSIGNED (type) + && wr[0].get_precision () < TYPE_PRECISION (sizetype)) + sgn = UNSIGNED; + + r[0] = offset_int::from (wr[0], sgn); + r[1] = offset_int::from (wr[1], sgn); + return true; +} + +/* Return the argument that the call STMT to a built-in function returns + or null if it doesn't. On success, set OFFRNG[] to the range of offsets + from the argument reflected in the value returned by the built-in if it + can be determined, otherwise to 0 and HWI_M1U respectively. Set + *PAST_END for functions like mempcpy that might return a past the end + pointer (most functions return a dereferenceable pointer to an existing + element of an array). */ + +static tree +gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end, + range_query *rvals) +{ + /* Clear and set below for the rare function(s) that might return + a past-the-end pointer. */ + *past_end = false; + + { + /* Check for attribute fn spec to see if the function returns one + of its arguments. */ + attr_fnspec fnspec = gimple_call_fnspec (as_a <gcall *>(stmt)); + unsigned int argno; + if (fnspec.returns_arg (&argno)) + { + /* Functions return the first argument (not a range). */ + offrng[0] = offrng[1] = 0; + return gimple_call_arg (stmt, argno); + } + } + + if (gimple_call_num_args (stmt) < 1) + return NULL_TREE; + + tree fn = gimple_call_fndecl (stmt); + if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) + { + /* See if this is a call to placement new. */ + if (!fn + || !DECL_IS_OPERATOR_NEW_P (fn) + || DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fn)) + return NULL_TREE; + + /* Check the mangling, keeping in mind that operator new takes + a size_t which could be unsigned int or unsigned long. */ + tree fname = DECL_ASSEMBLER_NAME (fn); + if (!id_equal (fname, "_ZnwjPv") // ordinary form + && !id_equal (fname, "_ZnwmPv") // ordinary form + && !id_equal (fname, "_ZnajPv") // array form + && !id_equal (fname, "_ZnamPv")) // array form + return NULL_TREE; + + if (gimple_call_num_args (stmt) != 2) + return NULL_TREE; + + /* Allocation functions return a pointer to the beginning. */ + offrng[0] = offrng[1] = 0; + return gimple_call_arg (stmt, 1); + } + + switch (DECL_FUNCTION_CODE (fn)) + { + case BUILT_IN_MEMCPY: + case BUILT_IN_MEMCPY_CHK: + case BUILT_IN_MEMMOVE: + case BUILT_IN_MEMMOVE_CHK: + case BUILT_IN_MEMSET: + case BUILT_IN_STRCAT: + case BUILT_IN_STRCAT_CHK: + case BUILT_IN_STRCPY: + case BUILT_IN_STRCPY_CHK: + case BUILT_IN_STRNCAT: + case BUILT_IN_STRNCAT_CHK: + case BUILT_IN_STRNCPY: + case BUILT_IN_STRNCPY_CHK: + /* Functions return the first argument (not a range). */ + offrng[0] = offrng[1] = 0; + return gimple_call_arg (stmt, 0); + + case BUILT_IN_MEMPCPY: + case BUILT_IN_MEMPCPY_CHK: + { + /* The returned pointer is in a range constrained by the smaller + of the upper bound of the size argument and the source object + size. */ + offrng[0] = 0; + offrng[1] = HOST_WIDE_INT_M1U; + tree off = gimple_call_arg (stmt, 2); + bool off_valid = get_offset_range (off, stmt, offrng, rvals); + if (!off_valid || offrng[0] != offrng[1]) + { + /* If the offset is either indeterminate or in some range, + try to constrain its upper bound to at most the size + of the source object. */ + access_ref aref; + tree src = gimple_call_arg (stmt, 1); + if (compute_objsize (src, 1, &aref, rvals) + && aref.sizrng[1] < offrng[1]) + offrng[1] = aref.sizrng[1]; + } + + /* Mempcpy may return a past-the-end pointer. */ + *past_end = true; + return gimple_call_arg (stmt, 0); + } + + case BUILT_IN_MEMCHR: + { + tree off = gimple_call_arg (stmt, 2); + if (get_offset_range (off, stmt, offrng, rvals)) + offrng[1] -= 1; + else + offrng[1] = HOST_WIDE_INT_M1U; + + offrng[0] = 0; + return gimple_call_arg (stmt, 0); + } + + case BUILT_IN_STRCHR: + case BUILT_IN_STRRCHR: + case BUILT_IN_STRSTR: + offrng[0] = 0; + offrng[1] = HOST_WIDE_INT_M1U; + return gimple_call_arg (stmt, 0); + + case BUILT_IN_STPCPY: + case BUILT_IN_STPCPY_CHK: + { + access_ref aref; + tree src = gimple_call_arg (stmt, 1); + if (compute_objsize (src, 1, &aref, rvals)) + offrng[1] = aref.sizrng[1] - 1; + else + offrng[1] = HOST_WIDE_INT_M1U; + + offrng[0] = 0; + return gimple_call_arg (stmt, 0); + } + + case BUILT_IN_STPNCPY: + case BUILT_IN_STPNCPY_CHK: + { + /* The returned pointer is in a range between the first argument + and it plus the smaller of the upper bound of the size argument + and the source object size. */ + offrng[1] = HOST_WIDE_INT_M1U; + tree off = gimple_call_arg (stmt, 2); + if (!get_offset_range (off, stmt, offrng, rvals) + || offrng[0] != offrng[1]) + { + /* If the offset is either indeterminate or in some range, + try to constrain its upper bound to at most the size + of the source object. */ + access_ref aref; + tree src = gimple_call_arg (stmt, 1); + if (compute_objsize (src, 1, &aref, rvals) + && aref.sizrng[1] < offrng[1]) + offrng[1] = aref.sizrng[1]; + } + + /* When the source is the empty string the returned pointer is + a copy of the argument. Otherwise stpcpy can also return + a past-the-end pointer. */ + offrng[0] = 0; + *past_end = true; + return gimple_call_arg (stmt, 0); + } + + default: + break; + } + + 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_range_query (cfun); + + 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. + When nonnull, uses RVALS for range information, otherwise gets global + range info. + Returns null when STMT is not a call to a valid allocation function. */ + +tree +gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, + range_query * /* = NULL */) +{ + if (!stmt || !is_gimple_call (stmt)) + return NULL_TREE; + + tree allocfntype; + if (tree fndecl = gimple_call_fndecl (stmt)) + allocfntype = TREE_TYPE (fndecl); + else + allocfntype = gimple_call_fntype (stmt); + + if (!allocfntype) + return NULL_TREE; + + unsigned argidx1 = UINT_MAX, argidx2 = UINT_MAX; + tree at = lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (allocfntype)); + if (!at) + { + if (!gimple_call_builtin_p (stmt, BUILT_IN_ALLOCA_WITH_ALIGN)) + return NULL_TREE; + + argidx1 = 0; + } + + unsigned nargs = gimple_call_num_args (stmt); + + if (argidx1 == UINT_MAX) + { + tree atval = TREE_VALUE (at); + if (!atval) + return NULL_TREE; + + argidx1 = TREE_INT_CST_LOW (TREE_VALUE (atval)) - 1; + if (nargs <= argidx1) + return NULL_TREE; + + atval = TREE_CHAIN (atval); + if (atval) + { + argidx2 = TREE_INT_CST_LOW (TREE_VALUE (atval)) - 1; + if (nargs <= argidx2) + return NULL_TREE; + } + } + + tree size = gimple_call_arg (stmt, argidx1); + + wide_int rng1_buf[2]; + /* If RNG1 is not set, use the buffer. */ + if (!rng1) + rng1 = rng1_buf; + + /* Use maximum precision to avoid overflow below. */ + const int prec = ADDR_MAX_PRECISION; + + { + tree r[2]; + /* Determine the largest valid range size, including zero. */ + if (!get_size_range (size, r, SR_ALLOW_ZERO | SR_USE_LARGEST)) + return NULL_TREE; + rng1[0] = wi::to_wide (r[0], prec); + rng1[1] = wi::to_wide (r[1], prec); + } + + if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST) + return fold_convert (sizetype, size); + + /* To handle ranges do the math in wide_int and return the product + of the upper bounds as a constant. Ignore anti-ranges. */ + tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node; + wide_int rng2[2]; + { + tree r[2]; + /* As above, use the full non-negative range on failure. */ + if (!get_size_range (n, r, SR_ALLOW_ZERO | SR_USE_LARGEST)) + return NULL_TREE; + rng2[0] = wi::to_wide (r[0], prec); + rng2[1] = wi::to_wide (r[1], prec); + } + + /* Compute products of both bounds for the caller but return the lesser + of SIZE_MAX and the product of the upper bounds as a constant. */ + rng1[0] = rng1[0] * rng2[0]; + rng1[1] = rng1[1] * rng2[1]; + + const tree size_max = TYPE_MAX_VALUE (sizetype); + if (wi::gtu_p (rng1[1], wi::to_wide (size_max, prec))) + { + rng1[1] = wi::to_wide (size_max, prec); + return size_max; + } + + return wide_int_to_tree (sizetype, rng1[1]); +} + +/* For an access to an object referenced to by the function parameter PTR + of pointer type, and set RNG[] to the range of sizes of the object + obtainedfrom the attribute access specification for the current function. + Set STATIC_ARRAY if the array parameter has been declared [static]. + Return the function parameter on success and null otherwise. */ + +tree +gimple_parm_array_size (tree ptr, wide_int rng[2], + bool *static_array /* = NULL */) +{ + /* For a function argument try to determine the byte size of the array + from the current function declaratation (e.g., attribute access or + related). */ + tree var = SSA_NAME_VAR (ptr); + if (TREE_CODE (var) != PARM_DECL) + return NULL_TREE; + + const unsigned prec = TYPE_PRECISION (sizetype); + + rdwr_map rdwr_idx; + attr_access *access = get_parm_access (rdwr_idx, var); + if (!access) + return NULL_TREE; + + if (access->sizarg != UINT_MAX) + { + /* TODO: Try to extract the range from the argument based on + those of subsequent assertions or based on known calls to + the current function. */ + return NULL_TREE; + } + + if (!access->minsize) + return NULL_TREE; + + /* Only consider ordinary array bound at level 2 (or above if it's + ever added). */ + if (warn_array_parameter < 2 && !access->static_p) + return NULL_TREE; + + if (static_array) + *static_array = access->static_p; + + rng[0] = wi::zero (prec); + rng[1] = wi::uhwi (access->minsize, prec); + /* Multiply the array bound encoded in the attribute by the size + of what the pointer argument to which it decays points to. */ + tree eltype = TREE_TYPE (TREE_TYPE (ptr)); + tree size = TYPE_SIZE_UNIT (eltype); + if (!size || TREE_CODE (size) != INTEGER_CST) + return NULL_TREE; + + rng[1] *= wi::to_wide (size, prec); + return var; +} + +access_ref::access_ref (tree bound /* = NULL_TREE */, + bool minaccess /* = false */) +: ref (), eval ([](tree x){ return x; }), deref (), trail1special (true), + base0 (true), parmarray () +{ + /* Set to valid. */ + offrng[0] = offrng[1] = 0; + offmax[0] = offmax[1] = 0; + /* Invalidate. */ + sizrng[0] = sizrng[1] = -1; + + /* Set the default bounds of the access and adjust below. */ + bndrng[0] = minaccess ? 1 : 0; + bndrng[1] = HOST_WIDE_INT_M1U; + + /* When BOUND is nonnull and a range can be extracted from it, + set the bounds of the access to reflect both it and MINACCESS. + BNDRNG[0] is the size of the minimum access. */ + tree rng[2]; + if (bound && get_size_range (bound, rng, SR_ALLOW_ZERO)) + { + bndrng[0] = wi::to_offset (rng[0]); + bndrng[1] = wi::to_offset (rng[1]); + bndrng[0] = bndrng[0] > 0 && minaccess ? 1 : 0; + } +} + +/* Return the PHI node REF refers to or null if it doesn't. */ + +gphi * +access_ref::phi () const +{ + if (!ref || TREE_CODE (ref) != SSA_NAME) + return NULL; + + gimple *def_stmt = SSA_NAME_DEF_STMT (ref); + if (!def_stmt || gimple_code (def_stmt) != GIMPLE_PHI) + return NULL; + + return as_a <gphi *> (def_stmt); +} + +/* Determine and return the largest object to which *THIS refers. If + *THIS refers to a PHI and PREF is nonnull, fill *PREF with the details + of the object determined by compute_objsize(ARG, OSTYPE) for each PHI + argument ARG. */ + +tree +access_ref::get_ref (vec<access_ref> *all_refs, + access_ref *pref /* = NULL */, + int ostype /* = 1 */, + ssa_name_limit_t *psnlim /* = NULL */, + pointer_query *qry /* = NULL */) const +{ + gphi *phi_stmt = this->phi (); + if (!phi_stmt) + return ref; + + /* FIXME: Calling get_ref() with a null PSNLIM is dangerous and might + cause unbounded recursion. */ + ssa_name_limit_t snlim_buf; + if (!psnlim) + psnlim = &snlim_buf; + + if (!psnlim->visit_phi (ref)) + return NULL_TREE; + + pointer_query empty_qry; + if (!qry) + qry = &empty_qry; + + /* The conservative result of the PHI reflecting the offset and size + of the largest PHI argument, regardless of whether or not they all + refer to the same object. */ + access_ref phi_ref; + if (pref) + { + /* The identity of the object has not been determined yet but + PREF->REF is set by the caller to the PHI for convenience. + The size is negative/invalid and the offset is zero (it's + updated only after the identity of the object has been + established). */ + gcc_assert (pref->sizrng[0] < 0); + gcc_assert (pref->offrng[0] == 0 && pref->offrng[1] == 0); + + phi_ref = *pref; + } + + /* Set if any argument is a function array (or VLA) parameter not + declared [static]. */ + bool parmarray = false; + /* The size of the smallest object referenced by the PHI arguments. */ + offset_int minsize = 0; + const offset_int maxobjsize = wi::to_offset (max_object_size ()); + + const unsigned nargs = gimple_phi_num_args (phi_stmt); + for (unsigned i = 0; i < nargs; ++i) + { + access_ref phi_arg_ref; + tree arg = gimple_phi_arg_def (phi_stmt, i); + if (!compute_objsize_r (arg, ostype, &phi_arg_ref, *psnlim, qry) + || phi_arg_ref.sizrng[0] < 0) + /* A PHI with all null pointer arguments. */ + return NULL_TREE; + + if (TREE_CODE (arg) == SSA_NAME) + qry->put_ref (arg, phi_arg_ref); + + if (all_refs) + all_refs->safe_push (phi_arg_ref); + + parmarray |= phi_arg_ref.parmarray; + + const bool nullp = integer_zerop (arg) && (i || i + 1 < nargs); + + if (phi_ref.sizrng[0] < 0) + { + /* If PHI_REF doesn't contain a meaningful result yet set it + to the result for the first argument. */ + if (!nullp) + phi_ref = phi_arg_ref; + + /* Set if the current argument refers to one or more objects of + known size (or range of sizes), as opposed to referring to + one or more unknown object(s). */ + const bool arg_known_size = (phi_arg_ref.sizrng[0] != 0 + || phi_arg_ref.sizrng[1] != maxobjsize); + if (arg_known_size) + minsize = phi_arg_ref.sizrng[0]; + + continue; + } + + const bool phi_known_size = (phi_ref.sizrng[0] != 0 + || phi_ref.sizrng[1] != maxobjsize); + + if (phi_known_size && phi_arg_ref.sizrng[0] < minsize) + minsize = phi_arg_ref.sizrng[0]; + + /* Disregard null pointers in PHIs with two or more arguments. + TODO: Handle this better! */ + if (nullp) + continue; + + /* Determine the amount of remaining space in the argument. */ + offset_int argrem[2]; + argrem[1] = phi_arg_ref.size_remaining (argrem); + + /* Determine the amount of remaining space computed so far and + if the remaining space in the argument is more use it instead. */ + offset_int phirem[2]; + phirem[1] = phi_ref.size_remaining (phirem); + + /* Reset the PHI's BASE0 flag if any of the nonnull arguments + refers to an object at an unknown offset. */ + if (!phi_arg_ref.base0) + phi_ref.base0 = false; + + if (phirem[1] < argrem[1] + || (phirem[1] == argrem[1] + && phi_ref.sizrng[1] < phi_arg_ref.sizrng[1])) + /* Use the argument with the most space remaining as the result, + or the larger one if the space is equal. */ + phi_ref = phi_arg_ref; + } + + /* Replace the lower bound of the largest argument with the size + of the smallest argument, and set PARMARRAY if any argument + was one. */ + phi_ref.sizrng[0] = minsize; + phi_ref.parmarray = parmarray; + + if (phi_ref.sizrng[0] < 0) + { + /* Fail if none of the PHI's arguments resulted in updating PHI_REF + (perhaps because they have all been already visited by prior + recursive calls). */ + psnlim->leave_phi (ref); + return NULL_TREE; + } + + /* Avoid changing *THIS. */ + if (pref && pref != this) + *pref = phi_ref; + + psnlim->leave_phi (ref); + + return phi_ref.ref; +} + +/* Return the maximum amount of space remaining and if non-null, set + argument to the minimum. */ + +offset_int +access_ref::size_remaining (offset_int *pmin /* = NULL */) const +{ + offset_int minbuf; + if (!pmin) + pmin = &minbuf; + + if (sizrng[0] < 0) + { + /* If the identity of the object hasn't been determined return + the maximum size range. */ + *pmin = 0; + return wi::to_offset (max_object_size ()); + } + + /* add_offset() ensures the offset range isn't inverted. */ + gcc_checking_assert (offrng[0] <= offrng[1]); + + if (base0) + { + /* The offset into referenced object is zero-based (i.e., it's + not referenced by a pointer into middle of some unknown object). */ + if (offrng[0] < 0 && offrng[1] < 0) + { + /* If the offset is negative the remaining size is zero. */ + *pmin = 0; + return 0; + } + + if (sizrng[1] <= offrng[0]) + { + /* If the starting offset is greater than or equal to the upper + bound on the size of the object, the space remaining is zero. + As a special case, if it's equal, set *PMIN to -1 to let + the caller know the offset is valid and just past the end. */ + *pmin = sizrng[1] == offrng[0] ? -1 : 0; + return 0; + } + + /* Otherwise return the size minus the lower bound of the offset. */ + offset_int or0 = offrng[0] < 0 ? 0 : offrng[0]; + + *pmin = sizrng[0] - or0; + return sizrng[1] - or0; + } + + /* The offset to the referenced object isn't zero-based (i.e., it may + refer to a byte other than the first. The size of such an object + is constrained only by the size of the address space (the result + of max_object_size()). */ + if (sizrng[1] <= offrng[0]) + { + *pmin = 0; + return 0; + } + + offset_int or0 = offrng[0] < 0 ? 0 : offrng[0]; + + *pmin = sizrng[0] - or0; + return sizrng[1] - or0; +} + +/* Return true if the offset and object size are in range for SIZE. */ + +bool +access_ref::offset_in_range (const offset_int &size) const +{ + if (size_remaining () < size) + return false; + + if (base0) + return offmax[0] >= 0 && offmax[1] <= sizrng[1]; + + offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + return offmax[0] > -maxoff && offmax[1] < maxoff; +} + +/* Add the range [MIN, MAX] to the offset range. For known objects (with + zero-based offsets) at least one of whose offset's bounds is in range, + constrain the other (or both) to the bounds of the object (i.e., zero + and the upper bound of its size). This improves the quality of + diagnostics. */ + +void access_ref::add_offset (const offset_int &min, const offset_int &max) +{ + if (min <= max) + { + /* To add an ordinary range just add it to the bounds. */ + offrng[0] += min; + offrng[1] += max; + } + else if (!base0) + { + /* To add an inverted range to an offset to an unknown object + expand it to the maximum. */ + add_max_offset (); + return; + } + else + { + /* To add an inverted range to an offset to an known object set + the upper bound to the maximum representable offset value + (which may be greater than MAX_OBJECT_SIZE). + The lower bound is either the sum of the current offset and + MIN when abs(MAX) is greater than the former, or zero otherwise. + Zero because then then inverted range includes the negative of + the lower bound. */ + offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + offrng[1] = maxoff; + + if (max >= 0) + { + offrng[0] = 0; + if (offmax[0] > 0) + offmax[0] = 0; + return; + } + + offset_int absmax = wi::abs (max); + if (offrng[0] < absmax) + { + offrng[0] += min; + /* Cap the lower bound at the upper (set to MAXOFF above) + to avoid inadvertently recreating an inverted range. */ + if (offrng[1] < offrng[0]) + offrng[0] = offrng[1]; + } + else + offrng[0] = 0; + } + + /* Set the minimum and maximmum computed so far. */ + if (offrng[1] < 0 && offrng[1] < offmax[0]) + offmax[0] = offrng[1]; + if (offrng[0] > 0 && offrng[0] > offmax[1]) + offmax[1] = offrng[0]; + + if (!base0) + return; + + /* When referencing a known object check to see if the offset computed + so far is in bounds... */ + offset_int remrng[2]; + remrng[1] = size_remaining (remrng); + if (remrng[1] > 0 || remrng[0] < 0) + { + /* ...if so, constrain it so that neither bound exceeds the size of + the object. Out of bounds offsets are left unchanged, and, for + better or worse, become in bounds later. They should be detected + and diagnosed at the point they first become invalid by + -Warray-bounds. */ + if (offrng[0] < 0) + offrng[0] = 0; + if (offrng[1] > sizrng[1]) + offrng[1] = sizrng[1]; + } +} + +/* Issue one inform message describing each target of an access REF. + WRITE is set for a write access and clear for a read access. */ + +void +access_ref::inform_access (access_mode mode) const +{ + const access_ref &aref = *this; + if (!aref.ref) + return; + + if (aref.phi ()) + { + /* Set MAXREF to refer to the largest object and fill ALL_REFS + with data for all objects referenced by the PHI arguments. */ + access_ref maxref; + auto_vec<access_ref> all_refs; + if (!get_ref (&all_refs, &maxref)) + return; + + /* Except for MAXREF, the rest of the arguments' offsets need not + reflect one added to the PHI itself. Determine the latter from + MAXREF on which the result is based. */ + const offset_int orng[] = + { + offrng[0] - maxref.offrng[0], + wi::smax (offrng[1] - maxref.offrng[1], offrng[0]), + }; + + /* Add the final PHI's offset to that of each of the arguments + and recurse to issue an inform message for it. */ + for (unsigned i = 0; i != all_refs.length (); ++i) + { + /* Skip any PHIs; those could lead to infinite recursion. */ + if (all_refs[i].phi ()) + continue; + + all_refs[i].add_offset (orng[0], orng[1]); + all_refs[i].inform_access (mode); + } + return; + } + + /* Convert offset range and avoid including a zero range since it + isn't necessarily meaningful. */ + HOST_WIDE_INT diff_min = tree_to_shwi (TYPE_MIN_VALUE (ptrdiff_type_node)); + HOST_WIDE_INT diff_max = tree_to_shwi (TYPE_MAX_VALUE (ptrdiff_type_node)); + HOST_WIDE_INT minoff; + HOST_WIDE_INT maxoff = diff_max; + if (wi::fits_shwi_p (aref.offrng[0])) + minoff = aref.offrng[0].to_shwi (); + else + minoff = aref.offrng[0] < 0 ? diff_min : diff_max; + + if (wi::fits_shwi_p (aref.offrng[1])) + maxoff = aref.offrng[1].to_shwi (); + + if (maxoff <= diff_min || maxoff >= diff_max) + /* Avoid mentioning an upper bound that's equal to or in excess + of the maximum of ptrdiff_t. */ + maxoff = minoff; + + /* Convert size range and always include it since all sizes are + meaningful. */ + unsigned long long minsize = 0, maxsize = 0; + if (wi::fits_shwi_p (aref.sizrng[0]) + && wi::fits_shwi_p (aref.sizrng[1])) + { + minsize = aref.sizrng[0].to_shwi (); + maxsize = aref.sizrng[1].to_shwi (); + } + + /* SIZRNG doesn't necessarily have the same range as the allocation + size determined by gimple_call_alloc_size (). */ + char sizestr[80]; + if (minsize == maxsize) + sprintf (sizestr, "%llu", minsize); + else + sprintf (sizestr, "[%llu, %llu]", minsize, maxsize); + + char offstr[80]; + if (minoff == 0 + && (maxoff == 0 || aref.sizrng[1] <= maxoff)) + offstr[0] = '\0'; + else if (minoff == maxoff) + sprintf (offstr, "%lli", (long long) minoff); + else + sprintf (offstr, "[%lli, %lli]", (long long) minoff, (long long) maxoff); + + location_t loc = UNKNOWN_LOCATION; + + tree ref = this->ref; + tree allocfn = NULL_TREE; + if (TREE_CODE (ref) == SSA_NAME) + { + gimple *stmt = SSA_NAME_DEF_STMT (ref); + if (!stmt) + return; + + if (is_gimple_call (stmt)) + { + loc = gimple_location (stmt); + if (gimple_call_builtin_p (stmt, BUILT_IN_ALLOCA_WITH_ALIGN)) + { + /* Strip the SSA_NAME suffix from the variable name and + recreate an identifier with the VLA's original name. */ + ref = gimple_call_lhs (stmt); + if (SSA_NAME_IDENTIFIER (ref)) + { + ref = SSA_NAME_IDENTIFIER (ref); + const char *id = IDENTIFIER_POINTER (ref); + size_t len = strcspn (id, ".$"); + if (!len) + len = strlen (id); + ref = get_identifier_with_length (id, len); + } + } + else + { + /* Except for VLAs, retrieve the allocation function. */ + allocfn = gimple_call_fndecl (stmt); + if (!allocfn) + allocfn = gimple_call_fn (stmt); + if (TREE_CODE (allocfn) == SSA_NAME) + { + /* For an ALLOC_CALL via a function pointer make a small + effort to determine the destination of the pointer. */ + gimple *def = SSA_NAME_DEF_STMT (allocfn); + if (gimple_assign_single_p (def)) + { + tree rhs = gimple_assign_rhs1 (def); + if (DECL_P (rhs)) + allocfn = rhs; + else if (TREE_CODE (rhs) == COMPONENT_REF) + allocfn = TREE_OPERAND (rhs, 1); + } + } + } + } + else if (gimple_nop_p (stmt)) + /* Handle DECL_PARM below. */ + ref = SSA_NAME_VAR (ref); + } + + if (DECL_P (ref)) + loc = DECL_SOURCE_LOCATION (ref); + else if (EXPR_P (ref) && EXPR_HAS_LOCATION (ref)) + loc = EXPR_LOCATION (ref); + else if (TREE_CODE (ref) != IDENTIFIER_NODE + && TREE_CODE (ref) != SSA_NAME) + return; + + if (mode == access_read_write || mode == access_write_only) + { + if (allocfn == NULL_TREE) + { + if (*offstr) + inform (loc, "at offset %s into destination object %qE of size %s", + offstr, ref, sizestr); + else + inform (loc, "destination object %qE of size %s", ref, sizestr); + return; + } + + if (*offstr) + inform (loc, + "at offset %s into destination object of size %s " + "allocated by %qE", offstr, sizestr, allocfn); + else + inform (loc, "destination object of size %s allocated by %qE", + sizestr, allocfn); + return; + } + + if (mode == access_read_only) + { + if (allocfn == NULL_TREE) + { + if (*offstr) + inform (loc, "at offset %s into source object %qE of size %s", + offstr, ref, sizestr); + else + inform (loc, "source object %qE of size %s", ref, sizestr); + + return; + } + + if (*offstr) + inform (loc, + "at offset %s into source object of size %s allocated by %qE", + offstr, sizestr, allocfn); + else + inform (loc, "source object of size %s allocated by %qE", + sizestr, allocfn); + return; + } + + if (allocfn == NULL_TREE) + { + if (*offstr) + inform (loc, "at offset %s into object %qE of size %s", + offstr, ref, sizestr); + else + inform (loc, "object %qE of size %s", ref, sizestr); + + return; + } + + if (*offstr) + inform (loc, + "at offset %s into object of size %s allocated by %qE", + offstr, sizestr, allocfn); + else + inform (loc, "object of size %s allocated by %qE", + sizestr, allocfn); +} + +/* Set a bit for the PHI in VISITED and return true if it wasn't + already set. */ + +bool +ssa_name_limit_t::visit_phi (tree ssa_name) +{ + if (!visited) + visited = BITMAP_ALLOC (NULL); + + /* Return false if SSA_NAME has already been visited. */ + return bitmap_set_bit (visited, SSA_NAME_VERSION (ssa_name)); +} + +/* Clear a bit for the PHI in VISITED. */ + +void +ssa_name_limit_t::leave_phi (tree ssa_name) +{ + /* Return false if SSA_NAME has already been visited. */ + bitmap_clear_bit (visited, SSA_NAME_VERSION (ssa_name)); +} + +/* Return false if the SSA_NAME chain length counter has reached + the limit, otherwise increment the counter and return true. */ + +bool +ssa_name_limit_t::next () +{ + /* Return a negative value to let caller avoid recursing beyond + the specified limit. */ + if (ssa_def_max == 0) + return false; + + --ssa_def_max; + return true; +} + +/* If the SSA_NAME has already been "seen" return a positive value. + Otherwise add it to VISITED. If the SSA_NAME limit has been + reached, return a negative value. Otherwise return zero. */ + +int +ssa_name_limit_t::next_phi (tree ssa_name) +{ + { + gimple *def_stmt = SSA_NAME_DEF_STMT (ssa_name); + /* Return a positive value if the PHI has already been visited. */ + if (gimple_code (def_stmt) == GIMPLE_PHI + && !visit_phi (ssa_name)) + return 1; + } + + /* Return a negative value to let caller avoid recursing beyond + the specified limit. */ + if (ssa_def_max == 0) + return -1; + + --ssa_def_max; + + return 0; +} + +ssa_name_limit_t::~ssa_name_limit_t () +{ + if (visited) + BITMAP_FREE (visited); +} + +/* Default ctor. Initialize object with pointers to the range_query + and cache_type instances to use or null. */ + +pointer_query::pointer_query (range_query *qry /* = NULL */, + cache_type *cache /* = NULL */) +: rvals (qry), var_cache (cache), hits (), misses (), + failures (), depth (), max_depth () +{ + /* No op. */ +} + +/* Return a pointer to the cached access_ref instance for the SSA_NAME + PTR if it's there or null otherwise. */ + +const access_ref * +pointer_query::get_ref (tree ptr, int ostype /* = 1 */) const +{ + if (!var_cache) + { + ++misses; + return NULL; + } + + unsigned version = SSA_NAME_VERSION (ptr); + unsigned idx = version << 1 | (ostype & 1); + if (var_cache->indices.length () <= idx) + { + ++misses; + return NULL; + } + + unsigned cache_idx = var_cache->indices[idx]; + if (var_cache->access_refs.length () <= cache_idx) + { + ++misses; + return NULL; + } + + access_ref &cache_ref = var_cache->access_refs[cache_idx]; + if (cache_ref.ref) + { + ++hits; + return &cache_ref; + } + + ++misses; + return NULL; +} + +/* Retrieve the access_ref instance for a variable from the cache if it's + there or compute it and insert it into the cache if it's nonnonull. */ + +bool +pointer_query::get_ref (tree ptr, access_ref *pref, int ostype /* = 1 */) +{ + const unsigned version + = TREE_CODE (ptr) == SSA_NAME ? SSA_NAME_VERSION (ptr) : 0; + + if (var_cache && version) + { + unsigned idx = version << 1 | (ostype & 1); + if (idx < var_cache->indices.length ()) + { + unsigned cache_idx = var_cache->indices[idx] - 1; + if (cache_idx < var_cache->access_refs.length () + && var_cache->access_refs[cache_idx].ref) + { + ++hits; + *pref = var_cache->access_refs[cache_idx]; + return true; + } + } + + ++misses; + } + + if (!compute_objsize (ptr, ostype, pref, this)) + { + ++failures; + return false; + } + + return true; +} + +/* Add a copy of the access_ref REF for the SSA_NAME to the cache if it's + nonnull. */ + +void +pointer_query::put_ref (tree ptr, const access_ref &ref, int ostype /* = 1 */) +{ + /* Only add populated/valid entries. */ + if (!var_cache || !ref.ref || ref.sizrng[0] < 0) + return; + + /* Add REF to the two-level cache. */ + unsigned version = SSA_NAME_VERSION (ptr); + unsigned idx = version << 1 | (ostype & 1); + + /* Grow INDICES if necessary. An index is valid if it's nonzero. + Its value minus one is the index into ACCESS_REFS. Not all + entries are valid. */ + if (var_cache->indices.length () <= idx) + var_cache->indices.safe_grow_cleared (idx + 1); + + if (!var_cache->indices[idx]) + var_cache->indices[idx] = var_cache->access_refs.length () + 1; + + /* Grow ACCESS_REF cache if necessary. An entry is valid if its + REF member is nonnull. All entries except for the last two + are valid. Once nonnull, the REF value must stay unchanged. */ + unsigned cache_idx = var_cache->indices[idx]; + if (var_cache->access_refs.length () <= cache_idx) + var_cache->access_refs.safe_grow_cleared (cache_idx + 1); + + access_ref &cache_ref = var_cache->access_refs[cache_idx]; + if (cache_ref.ref) + { + gcc_checking_assert (cache_ref.ref == ref.ref); + return; + } + + cache_ref = ref; +} + +/* Flush the cache if it's nonnull. */ + +void +pointer_query::flush_cache () +{ + if (!var_cache) + return; + var_cache->indices.release (); + var_cache->access_refs.release (); +} + +/* Dump statistics and, optionally, cache contents to DUMP_FILE. */ + +void +pointer_query::dump (FILE *dump_file, bool contents /* = false */) +{ + unsigned nused = 0, nrefs = 0; + unsigned nidxs = var_cache->indices.length (); + for (unsigned i = 0; i != nidxs; ++i) + { + unsigned ari = var_cache->indices[i]; + if (!ari) + continue; + + ++nused; + + const access_ref &aref = var_cache->access_refs[ari]; + if (!aref.ref) + continue; + + ++nrefs; + } + + fprintf (dump_file, "pointer_query counters:\n" + " index cache size: %u\n" + " index entries: %u\n" + " access cache size: %u\n" + " access entries: %u\n" + " hits: %u\n" + " misses: %u\n" + " failures: %u\n" + " max_depth: %u\n", + nidxs, nused, + var_cache->access_refs.length (), nrefs, + hits, misses, failures, max_depth); + + if (!contents || !nidxs) + return; + + fputs ("\npointer_query cache contents:\n", dump_file); + + for (unsigned i = 0; i != nidxs; ++i) + { + unsigned ari = var_cache->indices[i]; + if (!ari) + continue; + + const access_ref &aref = var_cache->access_refs[ari]; + if (!aref.ref) + continue; + + /* The level-1 cache index corresponds to the SSA_NAME_VERSION + shifted left by one and ORed with the Object Size Type in + the lowest bit. Print the two separately. */ + unsigned ver = i >> 1; + unsigned ost = i & 1; + + fprintf (dump_file, " %u.%u[%u]: ", ver, ost, ari); + if (tree name = ssa_name (ver)) + { + print_generic_expr (dump_file, name); + fputs (" = ", dump_file); + } + else + fprintf (dump_file, " _%u = ", ver); + + if (gphi *phi = aref.phi ()) + { + fputs ("PHI <", dump_file); + unsigned nargs = gimple_phi_num_args (phi); + for (unsigned i = 0; i != nargs; ++i) + { + tree arg = gimple_phi_arg_def (phi, i); + print_generic_expr (dump_file, arg); + if (i + 1 < nargs) + fputs (", ", dump_file); + } + fputc ('>', dump_file); + } + else + print_generic_expr (dump_file, aref.ref); + + if (aref.offrng[0] != aref.offrng[1]) + fprintf (dump_file, " + [%lli, %lli]", + (long long) aref.offrng[0].to_shwi (), + (long long) aref.offrng[1].to_shwi ()); + else if (aref.offrng[0] != 0) + fprintf (dump_file, " %c %lli", + aref.offrng[0] < 0 ? '-' : '+', + (long long) aref.offrng[0].to_shwi ()); + + fputc ('\n', dump_file); + } + + fputc ('\n', dump_file); +} + +/* A helper of compute_objsize_r() to determine the size from an assignment + statement STMT with the RHS of either MIN_EXPR or MAX_EXPR. */ + +static bool +handle_min_max_size (gimple *stmt, int ostype, access_ref *pref, + ssa_name_limit_t &snlim, pointer_query *qry) +{ + tree_code code = gimple_assign_rhs_code (stmt); + + tree ptr = gimple_assign_rhs1 (stmt); + + /* In a valid MAX_/MIN_EXPR both operands must refer to the same array. + Determine the size/offset of each and use the one with more or less + space remaining, respectively. If either fails, use the information + determined from the other instead, adjusted up or down as appropriate + for the expression. */ + access_ref aref[2] = { *pref, *pref }; + if (!compute_objsize_r (ptr, ostype, &aref[0], snlim, qry)) + { + aref[0].base0 = false; + aref[0].offrng[0] = aref[0].offrng[1] = 0; + aref[0].add_max_offset (); + aref[0].set_max_size_range (); + } + + ptr = gimple_assign_rhs2 (stmt); + if (!compute_objsize_r (ptr, ostype, &aref[1], snlim, qry)) + { + aref[1].base0 = false; + aref[1].offrng[0] = aref[1].offrng[1] = 0; + aref[1].add_max_offset (); + aref[1].set_max_size_range (); + } + + if (!aref[0].ref && !aref[1].ref) + /* Fail if the identity of neither argument could be determined. */ + return false; + + bool i0 = false; + if (aref[0].ref && aref[0].base0) + { + if (aref[1].ref && aref[1].base0) + { + /* If the object referenced by both arguments has been determined + set *PREF to the one with more or less space remainng, whichever + is appopriate for CODE. + TODO: Indicate when the objects are distinct so it can be + diagnosed. */ + i0 = code == MAX_EXPR; + const bool i1 = !i0; + + if (aref[i0].size_remaining () < aref[i1].size_remaining ()) + *pref = aref[i1]; + else + *pref = aref[i0]; + return true; + } + + /* If only the object referenced by one of the arguments could be + determined, use it and... */ + *pref = aref[0]; + i0 = true; + } + else + *pref = aref[1]; + + const bool i1 = !i0; + /* ...see if the offset obtained from the other pointer can be used + to tighten up the bound on the offset obtained from the first. */ + if ((code == MAX_EXPR && aref[i1].offrng[1] < aref[i0].offrng[0]) + || (code == MIN_EXPR && aref[i0].offrng[0] < aref[i1].offrng[1])) + { + pref->offrng[0] = aref[i0].offrng[0]; + pref->offrng[1] = aref[i0].offrng[1]; + } + return true; +} + +/* A helper of compute_objsize_r() to determine the size from ARRAY_REF + AREF. ADDR is true if PTR is the operand of ADDR_EXPR. Return true + on success and false on failure. */ + +static bool +handle_array_ref (tree aref, bool addr, int ostype, access_ref *pref, + ssa_name_limit_t &snlim, pointer_query *qry) +{ + gcc_assert (TREE_CODE (aref) == ARRAY_REF); + + ++pref->deref; + + tree arefop = TREE_OPERAND (aref, 0); + tree reftype = TREE_TYPE (arefop); + if (!addr && TREE_CODE (TREE_TYPE (reftype)) == POINTER_TYPE) + /* Avoid arrays of pointers. FIXME: Hande pointers to arrays + of known bound. */ + return false; + + if (!compute_objsize_r (arefop, ostype, pref, snlim, qry)) + return false; + + offset_int orng[2]; + tree off = pref->eval (TREE_OPERAND (aref, 1)); + range_query *const rvals = qry ? qry->rvals : NULL; + if (!get_offset_range (off, NULL, orng, rvals)) + { + /* Set ORNG to the maximum offset representable in ptrdiff_t. */ + orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + orng[0] = -orng[1] - 1; + } + + /* Convert the array index range determined above to a byte + offset. */ + tree lowbnd = array_ref_low_bound (aref); + if (!integer_zerop (lowbnd) && tree_fits_uhwi_p (lowbnd)) + { + /* Adjust the index by the low bound of the array domain + (normally zero but 1 in Fortran). */ + unsigned HOST_WIDE_INT lb = tree_to_uhwi (lowbnd); + orng[0] -= lb; + orng[1] -= lb; + } + + tree eltype = TREE_TYPE (aref); + tree tpsize = TYPE_SIZE_UNIT (eltype); + if (!tpsize || TREE_CODE (tpsize) != INTEGER_CST) + { + pref->add_max_offset (); + return true; + } + + offset_int sz = wi::to_offset (tpsize); + orng[0] *= sz; + orng[1] *= sz; + + if (ostype && TREE_CODE (eltype) == ARRAY_TYPE) + { + /* Except for the permissive raw memory functions which use + the size of the whole object determined above, use the size + of the referenced array. Because the overall offset is from + the beginning of the complete array object add this overall + offset to the size of array. */ + offset_int sizrng[2] = + { + pref->offrng[0] + orng[0] + sz, + pref->offrng[1] + orng[1] + sz + }; + if (sizrng[1] < sizrng[0]) + std::swap (sizrng[0], sizrng[1]); + if (sizrng[0] >= 0 && sizrng[0] <= pref->sizrng[0]) + pref->sizrng[0] = sizrng[0]; + if (sizrng[1] >= 0 && sizrng[1] <= pref->sizrng[1]) + pref->sizrng[1] = sizrng[1]; + } + + pref->add_offset (orng[0], orng[1]); + return true; +} + +/* A helper of compute_objsize_r() to determine the size from MEM_REF + MREF. Return true on success and false on failure. */ + +static bool +handle_mem_ref (tree mref, int ostype, access_ref *pref, + ssa_name_limit_t &snlim, pointer_query *qry) +{ + gcc_assert (TREE_CODE (mref) == MEM_REF); + + ++pref->deref; + + if (VECTOR_TYPE_P (TREE_TYPE (mref))) + { + /* Hack: Handle MEM_REFs of vector types as those to complete + objects; those may be synthesized from multiple assignments + to consecutive data members (see PR 93200 and 96963). + FIXME: Vectorized assignments should only be present after + vectorization so this hack is only necessary after it has + run and could be avoided in calls from prior passes (e.g., + tree-ssa-strlen.c). + FIXME: Deal with this more generally, e.g., by marking up + such MEM_REFs at the time they're created. */ + ostype = 0; + } + + tree mrefop = TREE_OPERAND (mref, 0); + if (!compute_objsize_r (mrefop, ostype, pref, snlim, qry)) + return false; + + offset_int orng[2]; + tree off = pref->eval (TREE_OPERAND (mref, 1)); + range_query *const rvals = qry ? qry->rvals : NULL; + if (!get_offset_range (off, NULL, orng, rvals)) + { + /* Set ORNG to the maximum offset representable in ptrdiff_t. */ + orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + orng[0] = -orng[1] - 1; + } + + pref->add_offset (orng[0], orng[1]); + return true; +} + +/* Helper to compute the size of the object referenced by the PTR + expression which must have pointer type, using Object Size type + OSTYPE (only the least significant 2 bits are used). + On success, sets PREF->REF to the DECL of the referenced object + if it's unique, otherwise to null, PREF->OFFRNG to the range of + offsets into it, and PREF->SIZRNG to the range of sizes of + the object(s). + SNLIM is used to avoid visiting the same PHI operand multiple + times, and, when nonnull, RVALS to determine range information. + Returns true on success, false when a meaningful size (or range) + cannot be determined. + + The function is intended for diagnostics and should not be used + to influence code generation or optimization. */ + +static bool +compute_objsize_r (tree ptr, int ostype, access_ref *pref, + ssa_name_limit_t &snlim, pointer_query *qry) +{ + STRIP_NOPS (ptr); + + const bool addr = TREE_CODE (ptr) == ADDR_EXPR; + if (addr) + { + --pref->deref; + ptr = TREE_OPERAND (ptr, 0); + } + + if (DECL_P (ptr)) + { + pref->ref = ptr; + + /* Reset the offset in case it was set by a prior call and not + cleared by the caller. The offset is only adjusted after + the identity of the object has been determined. */ + pref->offrng[0] = pref->offrng[1] = 0; + + if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr))) + { + /* Set the maximum size if the reference is to the pointer + itself (as opposed to what it points to), and clear + BASE0 since the offset isn't necessarily zero-based. */ + pref->set_max_size_range (); + pref->base0 = false; + return true; + } + + /* Valid offsets into the object are nonnegative. */ + pref->base0 = true; + + if (tree size = decl_init_size (ptr, false)) + if (TREE_CODE (size) == INTEGER_CST) + { + pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size); + return true; + } + + pref->set_max_size_range (); + return true; + } + + const tree_code code = TREE_CODE (ptr); + range_query *const rvals = qry ? qry->rvals : NULL; + + if (code == BIT_FIELD_REF) + { + tree ref = TREE_OPERAND (ptr, 0); + if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) + return false; + + offset_int off = wi::to_offset (pref->eval (TREE_OPERAND (ptr, 2))); + pref->add_offset (off / BITS_PER_UNIT); + return true; + } + + if (code == COMPONENT_REF) + { + tree ref = TREE_OPERAND (ptr, 0); + if (TREE_CODE (TREE_TYPE (ref)) == UNION_TYPE) + /* In accesses through union types consider the entire unions + rather than just their members. */ + ostype = 0; + tree field = TREE_OPERAND (ptr, 1); + + if (ostype == 0) + { + /* In OSTYPE zero (for raw memory functions like memcpy), use + the maximum size instead if the identity of the enclosing + object cannot be determined. */ + if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) + return false; + + /* Otherwise, use the size of the enclosing object and add + the offset of the member to the offset computed so far. */ + tree offset = byte_position (field); + if (TREE_CODE (offset) == INTEGER_CST) + pref->add_offset (wi::to_offset (offset)); + else + pref->add_max_offset (); + + if (!pref->ref) + /* REF may have been already set to an SSA_NAME earlier + to provide better context for diagnostics. In that case, + leave it unchanged. */ + pref->ref = ref; + return true; + } + + pref->ref = field; + + if (!addr && POINTER_TYPE_P (TREE_TYPE (field))) + { + /* Set maximum size if the reference is to the pointer member + itself (as opposed to what it points to). */ + pref->set_max_size_range (); + return true; + } + + /* SAM is set for array members that might need special treatment. */ + special_array_member sam; + tree size = component_ref_size (ptr, &sam); + if (sam == special_array_member::int_0) + pref->sizrng[0] = pref->sizrng[1] = 0; + else if (!pref->trail1special && sam == special_array_member::trail_1) + pref->sizrng[0] = pref->sizrng[1] = 1; + else if (size && TREE_CODE (size) == INTEGER_CST) + pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size); + else + { + /* When the size of the member is unknown it's either a flexible + array member or a trailing special array member (either zero + length or one-element). Set the size to the maximum minus + the constant size of the type. */ + pref->sizrng[0] = 0; + pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + if (tree recsize = TYPE_SIZE_UNIT (TREE_TYPE (ref))) + if (TREE_CODE (recsize) == INTEGER_CST) + pref->sizrng[1] -= wi::to_offset (recsize); + } + return true; + } + + if (code == ARRAY_REF) + return handle_array_ref (ptr, addr, ostype, pref, snlim, qry); + + if (code == MEM_REF) + return handle_mem_ref (ptr, ostype, pref, snlim, qry); + + if (code == TARGET_MEM_REF) + { + tree ref = TREE_OPERAND (ptr, 0); + if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) + return false; + + /* TODO: Handle remaining operands. Until then, add maximum offset. */ + pref->ref = ptr; + pref->add_max_offset (); + return true; + } + + if (code == INTEGER_CST) + { + /* Pointer constants other than null are most likely the result + of erroneous null pointer addition/subtraction. Set size to + zero. For null pointers, set size to the maximum for now + since those may be the result of jump threading. */ + if (integer_zerop (ptr)) + pref->set_max_size_range (); + else + pref->sizrng[0] = pref->sizrng[1] = 0; + pref->ref = ptr; + + return true; + } + + if (code == STRING_CST) + { + pref->sizrng[0] = pref->sizrng[1] = TREE_STRING_LENGTH (ptr); + pref->ref = ptr; + return true; + } + + if (code == POINTER_PLUS_EXPR) + { + tree ref = TREE_OPERAND (ptr, 0); + if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) + return false; + + /* Clear DEREF since the offset is being applied to the target + of the dereference. */ + pref->deref = 0; + + offset_int orng[2]; + tree off = pref->eval (TREE_OPERAND (ptr, 1)); + if (get_offset_range (off, NULL, orng, rvals)) + pref->add_offset (orng[0], orng[1]); + else + pref->add_max_offset (); + return true; + } + + if (code == VIEW_CONVERT_EXPR) + { + ptr = TREE_OPERAND (ptr, 0); + return compute_objsize_r (ptr, ostype, pref, snlim, qry); + } + + if (code == SSA_NAME) + { + if (!snlim.next ()) + return false; + + /* Only process an SSA_NAME if the recursion limit has not yet + been reached. */ + if (qry) + { + if (++qry->depth) + qry->max_depth = qry->depth; + if (const access_ref *cache_ref = qry->get_ref (ptr)) + { + /* If the pointer is in the cache set *PREF to what it refers + to and return success. + FIXME: BNDRNG is determined by each access and so it doesn't + belong in access_ref. Until the design is changed, keep it + unchanged here. */ + const offset_int bndrng[2] = { pref->bndrng[0], pref->bndrng[1] }; + *pref = *cache_ref; + pref->bndrng[0] = bndrng[0]; + pref->bndrng[1] = bndrng[1]; + return true; + } + } + + gimple *stmt = SSA_NAME_DEF_STMT (ptr); + if (is_gimple_call (stmt)) + { + /* If STMT is a call to an allocation function get the size + from its argument(s). If successful, also set *PREF->REF + to PTR for the caller to include in diagnostics. */ + wide_int wr[2]; + if (gimple_call_alloc_size (stmt, wr, rvals)) + { + pref->ref = ptr; + pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); + pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); + /* Constrain both bounds to a valid size. */ + offset_int maxsize = wi::to_offset (max_object_size ()); + if (pref->sizrng[0] > maxsize) + pref->sizrng[0] = maxsize; + if (pref->sizrng[1] > maxsize) + pref->sizrng[1] = maxsize; + } + else + { + /* For functions known to return one of their pointer arguments + try to determine what the returned pointer points to, and on + success add OFFRNG which was set to the offset added by + the function (e.g., memchr) to the overall offset. */ + bool past_end; + offset_int offrng[2]; + if (tree ret = gimple_call_return_array (stmt, offrng, + &past_end, rvals)) + { + if (!compute_objsize_r (ret, ostype, pref, snlim, qry)) + return false; + + /* Cap OFFRNG[1] to at most the remaining size of + the object. */ + offset_int remrng[2]; + remrng[1] = pref->size_remaining (remrng); + if (remrng[1] != 0 && !past_end) + /* Decrement the size for functions that never return + a past-the-end pointer. */ + remrng[1] -= 1; + + if (remrng[1] < offrng[1]) + offrng[1] = remrng[1]; + pref->add_offset (offrng[0], offrng[1]); + } + else + { + /* For other calls that might return arbitrary pointers + including into the middle of objects set the size + range to maximum, clear PREF->BASE0, and also set + PREF->REF to include in diagnostics. */ + pref->set_max_size_range (); + pref->base0 = false; + pref->ref = ptr; + } + } + qry->put_ref (ptr, *pref); + return true; + } + + if (gimple_nop_p (stmt)) + { + /* For a function argument try to determine the byte size + of the array from the current function declaratation + (e.g., attribute access or related). */ + wide_int wr[2]; + bool static_array = false; + if (tree ref = gimple_parm_array_size (ptr, wr, &static_array)) + { + pref->parmarray = !static_array; + pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); + pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); + pref->ref = ref; + qry->put_ref (ptr, *pref); + return true; + } + + pref->set_max_size_range (); + pref->base0 = false; + pref->ref = ptr; + qry->put_ref (ptr, *pref); + return true; + } + + if (gimple_code (stmt) == GIMPLE_PHI) + { + pref->ref = ptr; + access_ref phi_ref = *pref; + if (!pref->get_ref (NULL, &phi_ref, ostype, &snlim, qry)) + return false; + *pref = phi_ref; + pref->ref = ptr; + qry->put_ref (ptr, *pref); + return true; + } + + if (!is_gimple_assign (stmt)) + { + /* Clear BASE0 since the assigned pointer might point into + the middle of the object, set the maximum size range and, + if the SSA_NAME refers to a function argumnent, set + PREF->REF to it. */ + pref->base0 = false; + pref->set_max_size_range (); + pref->ref = ptr; + return true; + } + + tree_code code = gimple_assign_rhs_code (stmt); + + if (code == MAX_EXPR || code == MIN_EXPR) + { + if (!handle_min_max_size (stmt, ostype, pref, snlim, qry)) + return false; + qry->put_ref (ptr, *pref); + return true; + } + + tree rhs = gimple_assign_rhs1 (stmt); + + if (code == ASSERT_EXPR) + { + rhs = TREE_OPERAND (rhs, 0); + return compute_objsize_r (rhs, ostype, pref, snlim, qry); + } + + if (code == POINTER_PLUS_EXPR + && TREE_CODE (TREE_TYPE (rhs)) == POINTER_TYPE) + { + /* Compute the size of the object first. */ + if (!compute_objsize_r (rhs, ostype, pref, snlim, qry)) + return false; + + offset_int orng[2]; + tree off = gimple_assign_rhs2 (stmt); + if (get_offset_range (off, stmt, orng, rvals)) + pref->add_offset (orng[0], orng[1]); + else + pref->add_max_offset (); + + qry->put_ref (ptr, *pref); + return true; + } + + if (code == ADDR_EXPR || code == SSA_NAME) + { + if (!compute_objsize_r (rhs, ostype, pref, snlim, qry)) + return false; + qry->put_ref (ptr, *pref); + return true; + } + + /* (This could also be an assignment from a nonlocal pointer.) Save + PTR to mention in diagnostics but otherwise treat it as a pointer + to an unknown object. */ + pref->ref = rhs; + pref->base0 = false; + pref->set_max_size_range (); + return true; + } + + /* Assume all other expressions point into an unknown object + of the maximum valid size. */ + pref->ref = ptr; + pref->base0 = false; + pref->set_max_size_range (); + if (TREE_CODE (ptr) == SSA_NAME) + qry->put_ref (ptr, *pref); + return true; +} + +/* A "public" wrapper around the above. Clients should use this overload + instead. */ + +tree +compute_objsize (tree ptr, int ostype, access_ref *pref, + range_query *rvals /* = NULL */) +{ + pointer_query qry; + qry.rvals = rvals; + + /* Clear and invalidate in case *PREF is being reused. */ + pref->offrng[0] = pref->offrng[1] = 0; + pref->sizrng[0] = pref->sizrng[1] = -1; + + ssa_name_limit_t snlim; + if (!compute_objsize_r (ptr, ostype, pref, snlim, &qry)) + return NULL_TREE; + + offset_int maxsize = pref->size_remaining (); + if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0) + pref->offrng[0] = 0; + return wide_int_to_tree (sizetype, maxsize); +} + +/* Transitional wrapper. The function should be removed once callers + transition to the pointer_query API. */ + +tree +compute_objsize (tree ptr, int ostype, access_ref *pref, pointer_query *ptr_qry) +{ + pointer_query qry; + if (ptr_qry) + ptr_qry->depth = 0; + else + ptr_qry = &qry; + + /* Clear and invalidate in case *PREF is being reused. */ + pref->offrng[0] = pref->offrng[1] = 0; + pref->sizrng[0] = pref->sizrng[1] = -1; + + ssa_name_limit_t snlim; + if (!compute_objsize_r (ptr, ostype, pref, snlim, ptr_qry)) + return NULL_TREE; + + offset_int maxsize = pref->size_remaining (); + if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0) + pref->offrng[0] = 0; + return wide_int_to_tree (sizetype, maxsize); +} + +/* Legacy wrapper around the above. The function should be removed + once callers transition to one of the two above. */ + +tree +compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */, + tree *poff /* = NULL */, range_query *rvals /* = NULL */) +{ + /* Set the initial offsets to zero and size to negative to indicate + none has been computed yet. */ + access_ref ref; + tree size = compute_objsize (ptr, ostype, &ref, rvals); + if (!size || !ref.base0) + return NULL_TREE; + + if (pdecl) + *pdecl = ref.ref; + + if (poff) + *poff = wide_int_to_tree (ptrdiff_type_node, ref.offrng[ref.offrng[0] < 0]); + + return size; +} |