diff options
author | Ian Lance Taylor <iant@golang.org> | 2022-02-11 15:02:44 -0800 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2022-02-11 15:02:44 -0800 |
commit | 9a510fb0970d3d9a4201bce8965cabe67850386b (patch) | |
tree | 43d7fd2bbfd7ad8c9625a718a5e8718889351994 /gcc/tree-ssa-strlen.c | |
parent | a6d3012b274f38b20e2a57162106f625746af6c6 (diff) | |
parent | 8dc2499aa62f768c6395c9754b8cabc1ce25c494 (diff) | |
download | gcc-9a510fb0970d3d9a4201bce8965cabe67850386b.zip gcc-9a510fb0970d3d9a4201bce8965cabe67850386b.tar.gz gcc-9a510fb0970d3d9a4201bce8965cabe67850386b.tar.bz2 |
Merge from trunk revision 8dc2499aa62f768c6395c9754b8cabc1ce25c494
Diffstat (limited to 'gcc/tree-ssa-strlen.c')
-rw-r--r-- | gcc/tree-ssa-strlen.c | 5959 |
1 files changed, 0 insertions, 5959 deletions
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c deleted file mode 100644 index 2de7cb1..0000000 --- a/gcc/tree-ssa-strlen.c +++ /dev/null @@ -1,5959 +0,0 @@ -/* String length optimization - Copyright (C) 2011-2021 Free Software Foundation, Inc. - Contributed by Jakub Jelinek <jakub@redhat.com> - -This file is part of GCC. - -GCC is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 3, or (at your option) -any later version. - -GCC is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with GCC; see the file COPYING3. If not see -<http://www.gnu.org/licenses/>. */ - -#include "config.h" -#include "system.h" -#include "coretypes.h" -#include "backend.h" -#include "rtl.h" -#include "tree.h" -#include "gimple.h" -#include "alloc-pool.h" -#include "tree-pass.h" -#include "ssa.h" -#include "cgraph.h" -#include "gimple-pretty-print.h" -#include "gimple-ssa-warn-access.h" -#include "gimple-ssa-warn-restrict.h" -#include "fold-const.h" -#include "stor-layout.h" -#include "gimple-fold.h" -#include "tree-eh.h" -#include "gimplify.h" -#include "gimple-iterator.h" -#include "gimplify-me.h" -#include "expr.h" -#include "tree-cfg.h" -#include "tree-dfa.h" -#include "domwalk.h" -#include "tree-ssa-alias.h" -#include "tree-ssa-propagate.h" -#include "tree-ssa-strlen.h" -#include "tree-hash-traits.h" -#include "builtins.h" -#include "pointer-query.h" -#include "target.h" -#include "diagnostic-core.h" -#include "diagnostic.h" -#include "intl.h" -#include "attribs.h" -#include "calls.h" -#include "cfgloop.h" -#include "tree-ssa-loop.h" -#include "tree-scalar-evolution.h" -#include "vr-values.h" -#include "gimple-ssa-evrp-analyze.h" -#include "tree-ssa.h" - -/* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value - is an index into strinfo vector, negative value stands for - string length of a string literal (~strlen). */ -static vec<int> ssa_ver_to_stridx; - -/* Number of currently active string indexes plus one. */ -static int max_stridx; - -/* Set to true to optimize, false when just checking. */ -static bool strlen_optimize; - -/* String information record. */ -struct strinfo -{ - /* Number of leading characters that are known to be nonzero. This is - also the length of the string if FULL_STRING_P. - - The values in a list of related string pointers must be consistent; - that is, if strinfo B comes X bytes after strinfo A, it must be - the case that A->nonzero_chars == X + B->nonzero_chars. */ - tree nonzero_chars; - /* Any of the corresponding pointers for querying alias oracle. */ - tree ptr; - /* STMT is used for two things: - - - To record the statement that should be used for delayed length - computations. We maintain the invariant that all related strinfos - have delayed lengths or none do. - - - To record the malloc or calloc call that produced this result - to optimize away malloc/memset sequences. STMT is reset after - a calloc-allocated object has been stored a non-zero value into. */ - gimple *stmt; - /* Set to the dynamic allocation statement for the object (alloca, - calloc, malloc, or VLA). Unlike STMT, once set for a strinfo - object, ALLOC doesn't change. */ - gimple *alloc; - /* Pointer to '\0' if known, if NULL, it can be computed as - ptr + length. */ - tree endptr; - /* Reference count. Any changes to strinfo entry possibly shared - with dominating basic blocks need unshare_strinfo first, except - for dont_invalidate which affects only the immediately next - maybe_invalidate. */ - int refcount; - /* Copy of index. get_strinfo (si->idx) should return si; */ - int idx; - /* These 3 fields are for chaining related string pointers together. - E.g. for - bl = strlen (b); dl = strlen (d); strcpy (a, b); c = a + bl; - strcpy (c, d); e = c + dl; - strinfo(a) -> strinfo(c) -> strinfo(e) - All have ->first field equal to strinfo(a)->idx and are doubly - chained through prev/next fields. The later strinfos are required - to point into the same string with zero or more bytes after - the previous pointer and all bytes in between the two pointers - must be non-zero. Functions like strcpy or memcpy are supposed - to adjust all previous strinfo lengths, but not following strinfo - lengths (those are uncertain, usually invalidated during - maybe_invalidate, except when the alias oracle knows better). - Functions like strcat on the other side adjust the whole - related strinfo chain. - They are updated lazily, so to use the chain the same first fields - and si->prev->next == si->idx needs to be verified. */ - int first; - int next; - int prev; - /* A flag whether the string is known to be written in the current - function. */ - bool writable; - /* A flag for the next maybe_invalidate that this strinfo shouldn't - be invalidated. Always cleared by maybe_invalidate. */ - bool dont_invalidate; - /* True if the string is known to be nul-terminated after NONZERO_CHARS - characters. False is useful when detecting strings that are built - up via successive memcpys. */ - bool full_string_p; -}; - -/* Pool for allocating strinfo_struct entries. */ -static object_allocator<strinfo> strinfo_pool ("strinfo pool"); - -/* Vector mapping positive string indexes to strinfo, for the - current basic block. The first pointer in the vector is special, - it is either NULL, meaning the vector isn't shared, or it is - a basic block pointer to the owner basic_block if shared. - If some other bb wants to modify the vector, the vector needs - to be unshared first, and only the owner bb is supposed to free it. */ -static vec<strinfo *, va_heap, vl_embed> *stridx_to_strinfo; - -/* One OFFSET->IDX mapping. */ -struct stridxlist -{ - struct stridxlist *next; - HOST_WIDE_INT offset; - int idx; -}; - -/* Hash table entry, mapping a DECL to a chain of OFFSET->IDX mappings. */ -struct decl_stridxlist_map -{ - struct tree_map_base base; - struct stridxlist list; -}; - -/* Hash table for mapping decls to a chained list of offset -> idx - mappings. */ -typedef hash_map<tree_decl_hash, stridxlist> decl_to_stridxlist_htab_t; -static decl_to_stridxlist_htab_t *decl_to_stridxlist_htab; - -/* Hash table mapping strlen (or strnlen with constant bound and return - smaller than bound) calls to stridx instances describing - the calls' arguments. Non-null only when warn_stringop_truncation - is non-zero. */ -typedef std::pair<int, location_t> stridx_strlenloc; -static hash_map<tree, stridx_strlenloc> *strlen_to_stridx; - -/* Obstack for struct stridxlist and struct decl_stridxlist_map. */ -static struct obstack stridx_obstack; - -/* Last memcpy statement if it could be adjusted if the trailing - '\0' written is immediately overwritten, or - *x = '\0' store that could be removed if it is immediately overwritten. */ -struct laststmt_struct -{ - gimple *stmt; - tree len; - int stridx; -} laststmt; - -static int get_stridx_plus_constant (strinfo *, unsigned HOST_WIDE_INT, tree); - -/* Sets MINMAX to either the constant value or the range VAL is in - and returns either the constant value or VAL on success or null - when the range couldn't be determined. Uses RVALS or CFUN for - range info, whichever is nonnull. */ - -tree -get_range (tree val, gimple *stmt, wide_int minmax[2], - range_query *rvals /* = NULL */) -{ - if (!rvals) - { - if (!cfun) - /* When called from front ends for global initializers CFUN - may be null. */ - return NULL_TREE; - - rvals = get_range_query (cfun); - } - - value_range vr; - if (!rvals->range_of_expr (vr, val, stmt)) - return NULL_TREE; - - value_range_kind rng = vr.kind (); - if (rng == VR_RANGE) - { - /* Only handle straight ranges. */ - minmax[0] = wi::to_wide (vr.min ()); - minmax[1] = wi::to_wide (vr.max ()); - return val; - } - - return NULL_TREE; -} - -class strlen_pass : public dom_walker -{ -public: - strlen_pass (cdi_direction direction) - : dom_walker (direction), - evrp (false), - ptr_qry (&evrp, &var_cache), - var_cache (), - m_cleanup_cfg (false) - { - } - - ~strlen_pass (); - - virtual edge before_dom_children (basic_block); - virtual void after_dom_children (basic_block); - - bool check_and_optimize_stmt (bool *cleanup_eh); - bool check_and_optimize_call (bool *zero_write); - bool handle_assign (tree lhs, bool *zero_write); - bool handle_store (bool *zero_write); - void handle_pointer_plus (); - void handle_builtin_strlen (); - void handle_builtin_strchr (); - void handle_builtin_strcpy (built_in_function); - void handle_integral_assign (bool *cleanup_eh); - void handle_builtin_stxncpy_strncat (bool append_p); - void handle_builtin_memcpy (built_in_function bcode); - void handle_builtin_strcat (built_in_function bcode); - void handle_builtin_strncat (built_in_function); - bool handle_builtin_memset (bool *zero_write); - bool handle_builtin_memcmp (); - bool handle_builtin_string_cmp (); - void handle_alloc_call (built_in_function); - void maybe_warn_overflow (gimple *stmt, bool call_lhs, tree len, - strinfo *si = NULL, bool plus_one = false, - bool rawmem = false); - void maybe_warn_overflow (gimple *stmt, bool call_lhs, - unsigned HOST_WIDE_INT len, - strinfo *si = NULL, - bool plus_one = false, bool rawmem = false); - void adjust_last_stmt (strinfo *si, gimple *stmt, bool is_strcat); - tree strxcmp_eqz_result (gimple *stmt, tree arg1, int idx1, - tree arg2, int idx2, - unsigned HOST_WIDE_INT bound, - unsigned HOST_WIDE_INT len[2], - unsigned HOST_WIDE_INT *psize); - bool count_nonzero_bytes (tree expr_or_type, - unsigned lenrange[3], bool *nulterm, - bool *allnul, bool *allnonnul); - bool count_nonzero_bytes (tree exp, - unsigned HOST_WIDE_INT offset, - unsigned HOST_WIDE_INT nbytes, - unsigned lenrange[3], bool *nulterm, - bool *allnul, bool *allnonnul, - ssa_name_limit_t &snlim); - bool count_nonzero_bytes_addr (tree exp, - unsigned HOST_WIDE_INT offset, - unsigned HOST_WIDE_INT nbytes, - unsigned lenrange[3], bool *nulterm, - bool *allnul, bool *allnonnul, - ssa_name_limit_t &snlim); - bool get_len_or_size (gimple *stmt, tree arg, int idx, - unsigned HOST_WIDE_INT lenrng[2], - unsigned HOST_WIDE_INT *size, bool *nulterm); - - /* EVRP analyzer used for printf argument range processing, and to - track strlen results across integer variable assignments. */ - evrp_range_analyzer evrp; - - /* 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_stmt_iterator m_gsi; - - /* Flag that will trigger TODO_cleanup_cfg to be returned in strlen - execute function. */ - bool m_cleanup_cfg; -}; - -/* Return: - - * +1 if SI is known to start with more than OFF nonzero characters. - - * 0 if SI is known to start with exactly OFF nonzero characters. - - * -1 if SI either does not start with OFF nonzero characters - or the relationship between the number of leading nonzero - characters in SI and OFF is unknown. */ - -static int -compare_nonzero_chars (strinfo *si, unsigned HOST_WIDE_INT off) -{ - if (si->nonzero_chars - && TREE_CODE (si->nonzero_chars) == INTEGER_CST) - return compare_tree_int (si->nonzero_chars, off); - else - return -1; -} - -/* Same as above but suitable also for strings with non-constant lengths. - Uses RVALS to determine length range. */ - -static int -compare_nonzero_chars (strinfo *si, unsigned HOST_WIDE_INT off, - range_query *rvals) -{ - if (!si->nonzero_chars) - return -1; - - if (TREE_CODE (si->nonzero_chars) == INTEGER_CST) - return compare_tree_int (si->nonzero_chars, off); - - if (!rvals || TREE_CODE (si->nonzero_chars) != SSA_NAME) - return -1; - - value_range vr; - if (!rvals->range_of_expr (vr, si->nonzero_chars, si->stmt)) - return -1; - value_range_kind rng = vr.kind (); - if (rng != VR_RANGE) - return -1; - - /* If the offset is less than the minimum length or if the bounds - of the length range are equal return the result of the comparison - same as in the constant case. Otherwise return a conservative - result. */ - int cmpmin = compare_tree_int (vr.min (), off); - if (cmpmin > 0 || tree_int_cst_equal (vr.min (), vr.max ())) - return cmpmin; - - return -1; -} - -/* Return true if SI is known to be a zero-length string. */ - -static inline bool -zero_length_string_p (strinfo *si) -{ - return si->full_string_p && integer_zerop (si->nonzero_chars); -} - -/* Return strinfo vector entry IDX. */ - -static inline strinfo * -get_strinfo (int idx) -{ - if (vec_safe_length (stridx_to_strinfo) <= (unsigned int) idx) - return NULL; - return (*stridx_to_strinfo)[idx]; -} - -/* Get the next strinfo in the chain after SI, or null if none. */ - -static inline strinfo * -get_next_strinfo (strinfo *si) -{ - if (si->next == 0) - return NULL; - strinfo *nextsi = get_strinfo (si->next); - if (nextsi == NULL || nextsi->first != si->first || nextsi->prev != si->idx) - return NULL; - return nextsi; -} - -/* Helper function for get_stridx. Return the strinfo index of the address - of EXP, which is available in PTR if nonnull. If OFFSET_OUT, it is - OK to return the index for some X <= &EXP and store &EXP - X in - *OFFSET_OUT. When RVALS is nonnull uses it to determine range - information. */ - -static int -get_addr_stridx (tree exp, tree ptr, unsigned HOST_WIDE_INT *offset_out, - range_query *rvals = NULL) -{ - HOST_WIDE_INT off; - struct stridxlist *list, *last = NULL; - tree base; - - if (!decl_to_stridxlist_htab) - return 0; - - poly_int64 poff; - base = get_addr_base_and_unit_offset (exp, &poff); - if (base == NULL || !DECL_P (base) || !poff.is_constant (&off)) - return 0; - - list = decl_to_stridxlist_htab->get (base); - if (list == NULL) - return 0; - - do - { - if (list->offset == off) - { - if (offset_out) - *offset_out = 0; - return list->idx; - } - if (list->offset > off) - return 0; - last = list; - list = list->next; - } - while (list); - - if ((offset_out || ptr) && last && last->idx > 0) - { - unsigned HOST_WIDE_INT rel_off - = (unsigned HOST_WIDE_INT) off - last->offset; - strinfo *si = get_strinfo (last->idx); - if (si && compare_nonzero_chars (si, rel_off, rvals) >= 0) - { - if (offset_out) - { - *offset_out = rel_off; - return last->idx; - } - else - return get_stridx_plus_constant (si, rel_off, ptr); - } - } - return 0; -} - -/* Returns string index for EXP. When EXP is an SSA_NAME that refers - to a known strinfo with an offset and OFFRNG is non-null, sets - both elements of the OFFRNG array to the range of the offset and - returns the index of the known strinfo. In this case the result - must not be used in for functions that modify the string. - When nonnull, uses RVALS to determine range information. */ - -static int -get_stridx (tree exp, wide_int offrng[2] = NULL, range_query *rvals = NULL) -{ - if (offrng) - offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (ptrdiff_type_node)); - - if (TREE_CODE (exp) == SSA_NAME) - { - if (ssa_ver_to_stridx[SSA_NAME_VERSION (exp)]) - return ssa_ver_to_stridx[SSA_NAME_VERSION (exp)]; - - tree e = exp; - int last_idx = 0; - HOST_WIDE_INT offset = 0; - /* Follow a chain of at most 5 assignments. */ - for (int i = 0; i < 5; i++) - { - gimple *def_stmt = SSA_NAME_DEF_STMT (e); - if (!is_gimple_assign (def_stmt)) - return last_idx; - - tree_code rhs_code = gimple_assign_rhs_code (def_stmt); - tree ptr, off; - - if (rhs_code == ADDR_EXPR) - { - /* Handle indices/offsets into VLAs which are implemented - as pointers to arrays. */ - ptr = gimple_assign_rhs1 (def_stmt); - ptr = TREE_OPERAND (ptr, 0); - - /* Handle also VLAs of types larger than char. */ - if (tree eltsize = TYPE_SIZE_UNIT (TREE_TYPE (ptr))) - { - if (TREE_CODE (ptr) == ARRAY_REF) - { - off = TREE_OPERAND (ptr, 1); - ptr = TREE_OPERAND (ptr, 0); - if (!integer_onep (eltsize)) - { - /* Scale the array index by the size of the element - type in the rare case that it's greater than - the typical 1 for char, making sure both operands - have the same type. */ - eltsize = fold_convert (ssizetype, eltsize); - off = fold_convert (ssizetype, off); - off = fold_build2 (MULT_EXPR, ssizetype, off, eltsize); - } - } - else - off = integer_zero_node; - } - else - return 0; - - if (TREE_CODE (ptr) != MEM_REF) - return 0; - - /* Add the MEM_REF byte offset. */ - tree mem_off = TREE_OPERAND (ptr, 1); - off = fold_build2 (PLUS_EXPR, TREE_TYPE (off), off, mem_off); - ptr = TREE_OPERAND (ptr, 0); - } - else if (rhs_code == POINTER_PLUS_EXPR) - { - ptr = gimple_assign_rhs1 (def_stmt); - off = gimple_assign_rhs2 (def_stmt); - } - else - return 0; - - if (TREE_CODE (ptr) != SSA_NAME) - return 0; - - if (!tree_fits_shwi_p (off)) - { - if (int idx = ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)]) - if (offrng) - { - /* Only when requested by setting OFFRNG to non-null, - return the index corresponding to the SSA_NAME. - Do this irrespective of the whether the offset - is known. */ - if (get_range (off, def_stmt, offrng, rvals)) - { - /* When the offset range is known, increment it - it by the constant offset computed in prior - iterations and store it in the OFFRNG array. */ - offrng[0] += offset; - offrng[1] += offset; - } - else - { - /* When the offset range cannot be determined - store [0, SIZE_MAX] and let the caller decide - if the offset matters. */ - offrng[1] = wi::to_wide (TYPE_MAX_VALUE (sizetype)); - offrng[0] = wi::zero (offrng[1].get_precision ()); - } - return idx; - } - return 0; - } - - HOST_WIDE_INT this_off = tree_to_shwi (off); - if (offrng) - { - offrng[0] += wi::shwi (this_off, offrng->get_precision ()); - offrng[1] += offrng[0]; - } - - if (this_off < 0) - return last_idx; - - offset = (unsigned HOST_WIDE_INT) offset + this_off; - if (offset < 0) - return last_idx; - - if (int idx = ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)]) - { - strinfo *si = get_strinfo (idx); - if (si) - { - if (compare_nonzero_chars (si, offset) >= 0) - return get_stridx_plus_constant (si, offset, exp); - - if (offrng) - last_idx = idx; - } - } - e = ptr; - } - - return last_idx; - } - - if (TREE_CODE (exp) == ADDR_EXPR) - { - int idx = get_addr_stridx (TREE_OPERAND (exp, 0), exp, NULL); - if (idx != 0) - return idx; - } - - const char *p = c_getstr (exp); - if (p) - return ~(int) strlen (p); - - return 0; -} - -/* Return true if strinfo vector is shared with the immediate dominator. */ - -static inline bool -strinfo_shared (void) -{ - return vec_safe_length (stridx_to_strinfo) - && (*stridx_to_strinfo)[0] != NULL; -} - -/* Unshare strinfo vector that is shared with the immediate dominator. */ - -static void -unshare_strinfo_vec (void) -{ - strinfo *si; - unsigned int i = 0; - - gcc_assert (strinfo_shared ()); - stridx_to_strinfo = vec_safe_copy (stridx_to_strinfo); - for (i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i) - if (si != NULL) - si->refcount++; - (*stridx_to_strinfo)[0] = NULL; -} - -/* Attempt to create a string index for exp, ADDR_EXPR's operand. - Return a pointer to the location where the string index can - be stored (if 0) or is stored, or NULL if this can't be tracked. */ - -static int * -addr_stridxptr (tree exp) -{ - HOST_WIDE_INT off; - - poly_int64 poff; - tree base = get_addr_base_and_unit_offset (exp, &poff); - if (base == NULL_TREE || !DECL_P (base) || !poff.is_constant (&off)) - return NULL; - - if (!decl_to_stridxlist_htab) - { - decl_to_stridxlist_htab - = new hash_map<tree_decl_hash, stridxlist> (64); - gcc_obstack_init (&stridx_obstack); - } - - bool existed; - stridxlist *list = &decl_to_stridxlist_htab->get_or_insert (base, &existed); - if (existed) - { - int i; - stridxlist *before = NULL; - for (i = 0; i < 32; i++) - { - if (list->offset == off) - return &list->idx; - if (list->offset > off && before == NULL) - before = list; - if (list->next == NULL) - break; - list = list->next; - } - if (i == 32) - return NULL; - if (before) - { - list = before; - before = XOBNEW (&stridx_obstack, struct stridxlist); - *before = *list; - list->next = before; - list->offset = off; - list->idx = 0; - return &list->idx; - } - list->next = XOBNEW (&stridx_obstack, struct stridxlist); - list = list->next; - } - - list->next = NULL; - list->offset = off; - list->idx = 0; - return &list->idx; -} - -/* Create a new string index, or return 0 if reached limit. */ - -static int -new_stridx (tree exp) -{ - int idx; - if (max_stridx >= param_max_tracked_strlens) - return 0; - if (TREE_CODE (exp) == SSA_NAME) - { - if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (exp)) - return 0; - idx = max_stridx++; - ssa_ver_to_stridx[SSA_NAME_VERSION (exp)] = idx; - return idx; - } - if (TREE_CODE (exp) == ADDR_EXPR) - { - int *pidx = addr_stridxptr (TREE_OPERAND (exp, 0)); - if (pidx != NULL) - { - gcc_assert (*pidx == 0); - *pidx = max_stridx++; - return *pidx; - } - } - return 0; -} - -/* Like new_stridx, but for ADDR_EXPR's operand instead. */ - -static int -new_addr_stridx (tree exp) -{ - int *pidx; - if (max_stridx >= param_max_tracked_strlens) - return 0; - pidx = addr_stridxptr (exp); - if (pidx != NULL) - { - gcc_assert (*pidx == 0); - *pidx = max_stridx++; - return *pidx; - } - return 0; -} - -/* Create a new strinfo. */ - -static strinfo * -new_strinfo (tree ptr, int idx, tree nonzero_chars, bool full_string_p) -{ - strinfo *si = strinfo_pool.allocate (); - si->nonzero_chars = nonzero_chars; - STRIP_USELESS_TYPE_CONVERSION (ptr); - si->ptr = ptr; - si->stmt = NULL; - si->alloc = NULL; - si->endptr = NULL_TREE; - si->refcount = 1; - si->idx = idx; - si->first = 0; - si->prev = 0; - si->next = 0; - si->writable = false; - si->dont_invalidate = false; - si->full_string_p = full_string_p; - return si; -} - -/* Decrease strinfo refcount and free it if not referenced anymore. */ - -static inline void -free_strinfo (strinfo *si) -{ - if (si && --si->refcount == 0) - strinfo_pool.remove (si); -} - -/* Set strinfo in the vector entry IDX to SI. */ - -static inline void -set_strinfo (int idx, strinfo *si) -{ - if (vec_safe_length (stridx_to_strinfo) && (*stridx_to_strinfo)[0]) - unshare_strinfo_vec (); - if (vec_safe_length (stridx_to_strinfo) <= (unsigned int) idx) - vec_safe_grow_cleared (stridx_to_strinfo, idx + 1, true); - (*stridx_to_strinfo)[idx] = si; -} - -/* Return the first strinfo in the related strinfo chain - if all strinfos in between belong to the chain, otherwise NULL. */ - -static strinfo * -verify_related_strinfos (strinfo *origsi) -{ - strinfo *si = origsi, *psi; - - if (origsi->first == 0) - return NULL; - for (; si->prev; si = psi) - { - if (si->first != origsi->first) - return NULL; - psi = get_strinfo (si->prev); - if (psi == NULL) - return NULL; - if (psi->next != si->idx) - return NULL; - } - if (si->idx != si->first) - return NULL; - return si; -} - -/* Set SI's endptr to ENDPTR and compute its length based on SI->ptr. - Use LOC for folding. */ - -static void -set_endptr_and_length (location_t loc, strinfo *si, tree endptr) -{ - si->endptr = endptr; - si->stmt = NULL; - tree start_as_size = fold_convert_loc (loc, size_type_node, si->ptr); - tree end_as_size = fold_convert_loc (loc, size_type_node, endptr); - si->nonzero_chars = fold_build2_loc (loc, MINUS_EXPR, size_type_node, - end_as_size, start_as_size); - si->full_string_p = true; -} - -/* Return the string length, or NULL if it can't be computed. - The length may but need not be constant. Instead, it might be - the result of a strlen() call. */ - -static tree -get_string_length (strinfo *si) -{ - /* If the length has already been computed return it if it's exact - (i.e., the string is nul-terminated at NONZERO_CHARS), or return - null if it isn't. */ - if (si->nonzero_chars) - return si->full_string_p ? si->nonzero_chars : NULL; - - /* If the string is the result of one of the built-in calls below - attempt to compute the length from the call statement. */ - if (si->stmt) - { - gimple *stmt = si->stmt, *lenstmt; - tree callee, lhs, fn, tem; - location_t loc; - gimple_stmt_iterator gsi; - - gcc_assert (is_gimple_call (stmt)); - callee = gimple_call_fndecl (stmt); - gcc_assert (callee && fndecl_built_in_p (callee, BUILT_IN_NORMAL)); - lhs = gimple_call_lhs (stmt); - /* unshare_strinfo is intentionally not called here. The (delayed) - transformation of strcpy or strcat into stpcpy is done at the place - of the former strcpy/strcat call and so can affect all the strinfos - with the same stmt. If they were unshared before and transformation - has been already done, the handling of BUILT_IN_STPCPY{,_CHK} should - just compute the right length. */ - switch (DECL_FUNCTION_CODE (callee)) - { - case BUILT_IN_STRCAT: - case BUILT_IN_STRCAT_CHK: - gsi = gsi_for_stmt (stmt); - fn = builtin_decl_implicit (BUILT_IN_STRLEN); - gcc_assert (lhs == NULL_TREE); - tem = unshare_expr (gimple_call_arg (stmt, 0)); - lenstmt = gimple_build_call (fn, 1, tem); - lhs = make_ssa_name (TREE_TYPE (TREE_TYPE (fn)), lenstmt); - gimple_call_set_lhs (lenstmt, lhs); - gimple_set_vuse (lenstmt, gimple_vuse (stmt)); - gsi_insert_before (&gsi, lenstmt, GSI_SAME_STMT); - tem = gimple_call_arg (stmt, 0); - if (!ptrofftype_p (TREE_TYPE (lhs))) - { - lhs = convert_to_ptrofftype (lhs); - lhs = force_gimple_operand_gsi (&gsi, lhs, true, NULL_TREE, - true, GSI_SAME_STMT); - } - lenstmt = gimple_build_assign - (make_ssa_name (TREE_TYPE (gimple_call_arg (stmt, 0))), - POINTER_PLUS_EXPR,tem, lhs); - gsi_insert_before (&gsi, lenstmt, GSI_SAME_STMT); - gimple_call_set_arg (stmt, 0, gimple_assign_lhs (lenstmt)); - lhs = NULL_TREE; - /* FALLTHRU */ - case BUILT_IN_STRCPY: - case BUILT_IN_STRCPY_CHK: - gcc_assert (builtin_decl_implicit_p (BUILT_IN_STPCPY)); - if (gimple_call_num_args (stmt) == 2) - fn = builtin_decl_implicit (BUILT_IN_STPCPY); - else - fn = builtin_decl_explicit (BUILT_IN_STPCPY_CHK); - gcc_assert (lhs == NULL_TREE); - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "Optimizing: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - gimple_call_set_fndecl (stmt, fn); - lhs = make_ssa_name (TREE_TYPE (TREE_TYPE (fn)), stmt); - gimple_call_set_lhs (stmt, lhs); - update_stmt (stmt); - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "into: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - /* FALLTHRU */ - case BUILT_IN_STPCPY: - case BUILT_IN_STPCPY_CHK: - gcc_assert (lhs != NULL_TREE); - loc = gimple_location (stmt); - set_endptr_and_length (loc, si, lhs); - for (strinfo *chainsi = verify_related_strinfos (si); - chainsi != NULL; - chainsi = get_next_strinfo (chainsi)) - if (chainsi->nonzero_chars == NULL) - set_endptr_and_length (loc, chainsi, lhs); - break; - case BUILT_IN_ALLOCA: - case BUILT_IN_ALLOCA_WITH_ALIGN: - case BUILT_IN_MALLOC: - break; - /* BUILT_IN_CALLOC always has si->nonzero_chars set. */ - default: - gcc_unreachable (); - break; - } - } - - return si->nonzero_chars; -} - -/* Dump strlen data to FP for statement STMT. When non-null, RVALS - points to the valuation engine used to calculate ranges, and is - used to dump strlen range for non-constant results. */ - -DEBUG_FUNCTION void -dump_strlen_info (FILE *fp, gimple *stmt, range_query *rvals) -{ - if (stmt) - { - fprintf (fp, "\nDumping strlen pass data after "); - print_gimple_expr (fp, stmt, TDF_LINENO); - fputc ('\n', fp); - } - else - fprintf (fp, "\nDumping strlen pass data\n"); - - fprintf (fp, "max_stridx = %i\n", max_stridx); - fprintf (fp, "ssa_ver_to_stridx has %u elements\n", - ssa_ver_to_stridx.length ()); - fprintf (fp, "stridx_to_strinfo"); - if (stridx_to_strinfo) - { - fprintf (fp, " has %u elements\n", stridx_to_strinfo->length ()); - for (unsigned i = 0; i != stridx_to_strinfo->length (); ++i) - { - if (strinfo *si = (*stridx_to_strinfo)[i]) - { - if (!si->idx) - continue; - fprintf (fp, " idx = %i", si->idx); - if (si->ptr) - { - fprintf (fp, ", ptr = "); - print_generic_expr (fp, si->ptr); - } - - if (si->nonzero_chars) - { - fprintf (fp, ", nonzero_chars = "); - print_generic_expr (fp, si->nonzero_chars); - if (TREE_CODE (si->nonzero_chars) == SSA_NAME) - { - value_range_kind rng = VR_UNDEFINED; - wide_int min, max; - if (rvals) - { - value_range vr; - rvals->range_of_expr (vr, si->nonzero_chars, - si->stmt); - rng = vr.kind (); - if (range_int_cst_p (&vr)) - { - min = wi::to_wide (vr.min ()); - max = wi::to_wide (vr.max ()); - } - else - rng = VR_UNDEFINED; - } - else - { - value_range vr; - get_range_query (cfun) - ->range_of_expr (vr, si->nonzero_chars); - rng = vr.kind (); - if (!vr.undefined_p ()) - { - min = wi::to_wide (vr.min ()); - max = wi::to_wide (vr.max ()); - } - } - - if (rng == VR_RANGE || rng == VR_ANTI_RANGE) - { - fprintf (fp, " %s[%llu, %llu]", - rng == VR_RANGE ? "" : "~", - (long long) min.to_uhwi (), - (long long) max.to_uhwi ()); - } - } - } - - fprintf (fp, ", refcount = %i", si->refcount); - if (si->stmt) - { - fprintf (fp, ", stmt = "); - print_gimple_expr (fp, si->stmt, 0); - } - if (si->alloc) - { - fprintf (fp, ", alloc = "); - print_gimple_expr (fp, si->alloc, 0); - } - if (si->writable) - fprintf (fp, ", writable"); - if (si->dont_invalidate) - fprintf (fp, ", dont_invalidate"); - if (si->full_string_p) - fprintf (fp, ", full_string_p"); - if (strinfo *next = get_next_strinfo (si)) - { - fprintf (fp, ", {"); - do - fprintf (fp, "%i%s", next->idx, next->first ? ", " : ""); - while ((next = get_next_strinfo (next))); - fprintf (fp, "}"); - } - fputs ("\n", fp); - } - } - } - else - fprintf (fp, " = null\n"); - - fprintf (fp, "decl_to_stridxlist_htab"); - if (decl_to_stridxlist_htab) - { - fputs ("\n", fp); - typedef decl_to_stridxlist_htab_t::iterator iter_t; - for (iter_t it = decl_to_stridxlist_htab->begin (); - it != decl_to_stridxlist_htab->end (); ++it) - { - tree decl = (*it).first; - stridxlist *list = &(*it).second; - fprintf (fp, " decl = "); - print_generic_expr (fp, decl); - if (list) - { - fprintf (fp, ", offsets = {"); - for (; list; list = list->next) - fprintf (fp, "%lli%s", (long long) list->offset, - list->next ? ", " : ""); - fputs ("}", fp); - } - fputs ("\n", fp); - } - } - else - fprintf (fp, " = null\n"); - - if (laststmt.stmt) - { - fprintf (fp, "laststmt = "); - print_gimple_expr (fp, laststmt.stmt, 0); - fprintf (fp, ", len = "); - print_generic_expr (fp, laststmt.len); - fprintf (fp, ", stridx = %i\n", laststmt.stridx); - } -} - -/* Attempt to determine the length of the string SRC. On success, store - the length in *PDATA and return true. Otherwise, return false. - VISITED is a bitmap of visited PHI nodes. RVALS points to the valuation - engine used to calculate ranges. PSSA_DEF_MAX to an SSA_NAME - assignment limit used to prevent runaway recursion. */ - -static bool -get_range_strlen_dynamic (tree src, gimple *stmt, - c_strlen_data *pdata, bitmap *visited, - range_query *rvals, unsigned *pssa_def_max) -{ - int idx = get_stridx (src); - if (!idx) - { - if (TREE_CODE (src) == SSA_NAME) - { - gimple *def_stmt = SSA_NAME_DEF_STMT (src); - if (gimple_code (def_stmt) == GIMPLE_PHI) - { - if (!*visited) - *visited = BITMAP_ALLOC (NULL); - - if (!bitmap_set_bit (*visited, SSA_NAME_VERSION (src))) - return true; - - if (*pssa_def_max == 0) - return false; - - --*pssa_def_max; - - /* Iterate over the PHI arguments and determine the minimum - and maximum length/size of each and incorporate them into - the overall result. */ - gphi *phi = as_a <gphi *> (def_stmt); - for (unsigned i = 0; i != gimple_phi_num_args (phi); ++i) - { - tree arg = gimple_phi_arg_def (phi, i); - if (arg == gimple_phi_result (def_stmt)) - continue; - - c_strlen_data argdata = { }; - if (get_range_strlen_dynamic (arg, phi, &argdata, visited, - rvals, pssa_def_max)) - { - /* Set the DECL of an unterminated array this argument - refers to if one hasn't been found yet. */ - if (!pdata->decl && argdata.decl) - pdata->decl = argdata.decl; - - if (!argdata.minlen - || (integer_zerop (argdata.minlen) - && (!argdata.maxbound - || integer_all_onesp (argdata.maxbound)) - && integer_all_onesp (argdata.maxlen))) - { - /* Set the upper bound of the length to unbounded. */ - pdata->maxlen = build_all_ones_cst (size_type_node); - continue; - } - - /* Adjust the minimum and maximum length determined - so far and the upper bound on the array size. */ - if (!pdata->minlen - || tree_int_cst_lt (argdata.minlen, pdata->minlen)) - pdata->minlen = argdata.minlen; - if (!pdata->maxlen - || (argdata.maxlen - && tree_int_cst_lt (pdata->maxlen, argdata.maxlen))) - pdata->maxlen = argdata.maxlen; - if (!pdata->maxbound - || TREE_CODE (pdata->maxbound) != INTEGER_CST - || (argdata.maxbound - && tree_int_cst_lt (pdata->maxbound, - argdata.maxbound) - && !integer_all_onesp (argdata.maxbound))) - pdata->maxbound = argdata.maxbound; - } - else - pdata->maxlen = build_all_ones_cst (size_type_node); - } - - return true; - } - } - - /* Return success regardless of the result and handle *PDATA - in the caller. */ - get_range_strlen (src, pdata, 1); - return true; - } - - if (idx < 0) - { - /* SRC is a string of constant length. */ - pdata->minlen = build_int_cst (size_type_node, ~idx); - pdata->maxlen = pdata->minlen; - pdata->maxbound = pdata->maxlen; - return true; - } - - if (strinfo *si = get_strinfo (idx)) - { - pdata->minlen = get_string_length (si); - if (!pdata->minlen && si->nonzero_chars) - { - if (TREE_CODE (si->nonzero_chars) == INTEGER_CST) - pdata->minlen = si->nonzero_chars; - else if (TREE_CODE (si->nonzero_chars) == SSA_NAME) - { - value_range vr; - rvals->range_of_expr (vr, si->nonzero_chars, si->stmt); - if (range_int_cst_p (&vr)) - { - pdata->minlen = vr.min (); - pdata->maxlen = vr.max (); - } - else - pdata->minlen = build_zero_cst (size_type_node); - } - else - pdata->minlen = build_zero_cst (size_type_node); - - tree base = si->ptr; - if (TREE_CODE (base) == ADDR_EXPR) - base = TREE_OPERAND (base, 0); - - HOST_WIDE_INT off; - poly_int64 poff; - base = get_addr_base_and_unit_offset (base, &poff); - if (base - && DECL_P (base) - && TREE_CODE (TREE_TYPE (base)) == ARRAY_TYPE - && TYPE_SIZE_UNIT (TREE_TYPE (base)) - && poff.is_constant (&off)) - { - tree basetype = TREE_TYPE (base); - tree size = TYPE_SIZE_UNIT (basetype); - if (TREE_CODE (size) == INTEGER_CST) - { - ++off; /* Increment for the terminating nul. */ - tree toffset = build_int_cst (size_type_node, off); - pdata->maxlen = fold_build2 (MINUS_EXPR, size_type_node, size, - toffset); - pdata->maxbound = pdata->maxlen; - } - else - pdata->maxlen = build_all_ones_cst (size_type_node); - } - else - pdata->maxlen = build_all_ones_cst (size_type_node); - } - else if (pdata->minlen && TREE_CODE (pdata->minlen) == SSA_NAME) - { - value_range vr; - rvals->range_of_expr (vr, si->nonzero_chars, stmt); - if (range_int_cst_p (&vr)) - { - pdata->minlen = vr.min (); - pdata->maxlen = vr.max (); - pdata->maxbound = pdata->maxlen; - } - else - { - pdata->minlen = build_zero_cst (size_type_node); - pdata->maxlen = build_all_ones_cst (size_type_node); - } - } - else if (pdata->minlen && TREE_CODE (pdata->minlen) == INTEGER_CST) - { - pdata->maxlen = pdata->minlen; - pdata->maxbound = pdata->minlen; - } - else - { - /* For PDATA->MINLEN that's a non-constant expression such - as PLUS_EXPR whose value range is unknown, set the bounds - to zero and SIZE_MAX. */ - pdata->minlen = build_zero_cst (size_type_node); - pdata->maxlen = build_all_ones_cst (size_type_node); - } - - return true; - } - - return false; -} - -/* Analogous to get_range_strlen but for dynamically created strings, - i.e., those created by calls to strcpy as opposed to just string - constants. - Try to obtain the range of the lengths of the string(s) referenced - by SRC, or the size of the largest array SRC refers to if the range - of lengths cannot be determined, and store all in *PDATA. RVALS - points to the valuation engine used to calculate ranges. */ - -void -get_range_strlen_dynamic (tree src, gimple *stmt, c_strlen_data *pdata, - range_query *rvals) -{ - bitmap visited = NULL; - tree maxbound = pdata->maxbound; - - unsigned limit = param_ssa_name_def_chain_limit; - if (!get_range_strlen_dynamic (src, stmt, pdata, &visited, rvals, &limit)) - { - /* On failure extend the length range to an impossible maximum - (a valid MAXLEN must be less than PTRDIFF_MAX - 1). Other - members can stay unchanged regardless. */ - pdata->minlen = ssize_int (0); - pdata->maxlen = build_all_ones_cst (size_type_node); - } - else if (!pdata->minlen) - pdata->minlen = ssize_int (0); - - /* If it's unchanged from it initial non-null value, set the conservative - MAXBOUND to SIZE_MAX. Otherwise leave it null (if it is null). */ - if (maxbound && pdata->maxbound == maxbound) - pdata->maxbound = build_all_ones_cst (size_type_node); - - if (visited) - BITMAP_FREE (visited); -} - -/* Invalidate string length information for strings whose length might - change due to stores in STMT, except those marked DONT_INVALIDATE. - For string-modifying statements, ZERO_WRITE is set when the statement - wrote only zeros. - Returns true if any STRIDX_TO_STRINFO entries were considered - for invalidation. */ - -static bool -maybe_invalidate (gimple *stmt, bool zero_write = false) -{ - if (dump_file && (dump_flags & TDF_DETAILS)) - { - fprintf (dump_file, "%s called for ", __func__); - print_gimple_stmt (dump_file, stmt, TDF_LINENO); - } - - strinfo *si; - bool nonempty = false; - - for (unsigned i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i) - { - if (si == NULL || !POINTER_TYPE_P (TREE_TYPE (si->ptr))) - continue; - - nonempty = true; - - /* Unconditionally reset DONT_INVALIDATE. */ - bool dont_invalidate = si->dont_invalidate; - si->dont_invalidate = false; - - if (dont_invalidate) - continue; - - ao_ref r; - tree size = si->nonzero_chars; - ao_ref_init_from_ptr_and_size (&r, si->ptr, size); - /* Include the terminating nul in the size of the string - to consider when determining possible clobber. But do not - add it to 'size' since we don't know whether it would - actually fit the allocated area. */ - if (known_size_p (r.size)) - { - if (known_le (r.size, HOST_WIDE_INT_MAX - BITS_PER_UNIT)) - r.max_size += BITS_PER_UNIT; - else - r.max_size = -1; - } - if (stmt_may_clobber_ref_p_1 (stmt, &r)) - { - if (dump_file && (dump_flags & TDF_DETAILS)) - { - fputs (" statement may clobber object ", dump_file); - print_generic_expr (dump_file, si->ptr); - if (size && tree_fits_uhwi_p (size)) - fprintf (dump_file, " " HOST_WIDE_INT_PRINT_UNSIGNED - " bytes in size", tree_to_uhwi (size)); - fputc ('\n', dump_file); - } - - set_strinfo (i, NULL); - free_strinfo (si); - continue; - } - - if (size - && !zero_write - && si->stmt - && is_gimple_call (si->stmt) - && (DECL_FUNCTION_CODE (gimple_call_fndecl (si->stmt)) - == BUILT_IN_CALLOC)) - { - /* If the clobber test above considered the length of - the string (including the nul), then for (potentially) - non-zero writes that might modify storage allocated by - calloc consider the whole object and if it might be - clobbered by the statement reset the statement. */ - ao_ref_init_from_ptr_and_size (&r, si->ptr, NULL_TREE); - if (stmt_may_clobber_ref_p_1 (stmt, &r)) - si->stmt = NULL; - } - } - - if (dump_file && (dump_flags & TDF_DETAILS)) - fprintf (dump_file, "%s returns %i\n", __func__, nonempty); - - return nonempty; -} - -/* Unshare strinfo record SI, if it has refcount > 1 or - if stridx_to_strinfo vector is shared with some other - bbs. */ - -static strinfo * -unshare_strinfo (strinfo *si) -{ - strinfo *nsi; - - if (si->refcount == 1 && !strinfo_shared ()) - return si; - - nsi = new_strinfo (si->ptr, si->idx, si->nonzero_chars, si->full_string_p); - nsi->stmt = si->stmt; - nsi->alloc = si->alloc; - nsi->endptr = si->endptr; - nsi->first = si->first; - nsi->prev = si->prev; - nsi->next = si->next; - nsi->writable = si->writable; - set_strinfo (si->idx, nsi); - free_strinfo (si); - return nsi; -} - -/* Attempt to create a new strinfo for BASESI + OFF, or find existing - strinfo if there is any. Return it's idx, or 0 if no strinfo has - been created. */ - -static int -get_stridx_plus_constant (strinfo *basesi, unsigned HOST_WIDE_INT off, - tree ptr) -{ - if (TREE_CODE (ptr) == SSA_NAME && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (ptr)) - return 0; - - if (compare_nonzero_chars (basesi, off) < 0 - || !tree_fits_uhwi_p (basesi->nonzero_chars)) - return 0; - - unsigned HOST_WIDE_INT nonzero_chars - = tree_to_uhwi (basesi->nonzero_chars) - off; - strinfo *si = basesi, *chainsi; - if (si->first || si->prev || si->next) - si = verify_related_strinfos (basesi); - if (si == NULL - || si->nonzero_chars == NULL_TREE - || TREE_CODE (si->nonzero_chars) != INTEGER_CST) - return 0; - - if (TREE_CODE (ptr) == SSA_NAME - && ssa_ver_to_stridx.length () <= SSA_NAME_VERSION (ptr)) - ssa_ver_to_stridx.safe_grow_cleared (num_ssa_names, true); - - gcc_checking_assert (compare_tree_int (si->nonzero_chars, off) != -1); - for (chainsi = si; chainsi->next; chainsi = si) - { - si = get_next_strinfo (chainsi); - if (si == NULL - || si->nonzero_chars == NULL_TREE - || TREE_CODE (si->nonzero_chars) != INTEGER_CST) - break; - int r = compare_tree_int (si->nonzero_chars, nonzero_chars); - if (r != 1) - { - if (r == 0) - { - if (TREE_CODE (ptr) == SSA_NAME) - ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)] = si->idx; - else - { - int *pidx = addr_stridxptr (TREE_OPERAND (ptr, 0)); - if (pidx != NULL && *pidx == 0) - *pidx = si->idx; - } - return si->idx; - } - break; - } - } - - int idx = new_stridx (ptr); - if (idx == 0) - return 0; - si = new_strinfo (ptr, idx, build_int_cst (size_type_node, nonzero_chars), - basesi->full_string_p); - set_strinfo (idx, si); - if (strinfo *nextsi = get_strinfo (chainsi->next)) - { - nextsi = unshare_strinfo (nextsi); - si->next = nextsi->idx; - nextsi->prev = idx; - } - chainsi = unshare_strinfo (chainsi); - if (chainsi->first == 0) - chainsi->first = chainsi->idx; - chainsi->next = idx; - if (chainsi->endptr == NULL_TREE && zero_length_string_p (si)) - chainsi->endptr = ptr; - si->endptr = chainsi->endptr; - si->prev = chainsi->idx; - si->first = chainsi->first; - si->writable = chainsi->writable; - return si->idx; -} - -/* Note that PTR, a pointer SSA_NAME initialized in the current stmt, points - to a zero-length string and if possible chain it to a related strinfo - chain whose part is or might be CHAINSI. */ - -static strinfo * -zero_length_string (tree ptr, strinfo *chainsi) -{ - strinfo *si; - int idx; - if (ssa_ver_to_stridx.length () <= SSA_NAME_VERSION (ptr)) - ssa_ver_to_stridx.safe_grow_cleared (num_ssa_names, true); - gcc_checking_assert (TREE_CODE (ptr) == SSA_NAME - && ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)] == 0); - - if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (ptr)) - return NULL; - if (chainsi != NULL) - { - si = verify_related_strinfos (chainsi); - if (si) - { - do - { - /* We shouldn't mix delayed and non-delayed lengths. */ - gcc_assert (si->full_string_p); - if (si->endptr == NULL_TREE) - { - si = unshare_strinfo (si); - si->endptr = ptr; - } - chainsi = si; - si = get_next_strinfo (si); - } - while (si != NULL); - if (zero_length_string_p (chainsi)) - { - if (chainsi->next) - { - chainsi = unshare_strinfo (chainsi); - chainsi->next = 0; - } - ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)] = chainsi->idx; - return chainsi; - } - } - else - { - /* We shouldn't mix delayed and non-delayed lengths. */ - gcc_assert (chainsi->full_string_p); - if (chainsi->first || chainsi->prev || chainsi->next) - { - chainsi = unshare_strinfo (chainsi); - chainsi->first = 0; - chainsi->prev = 0; - chainsi->next = 0; - } - } - } - idx = new_stridx (ptr); - if (idx == 0) - return NULL; - si = new_strinfo (ptr, idx, build_int_cst (size_type_node, 0), true); - set_strinfo (idx, si); - si->endptr = ptr; - if (chainsi != NULL) - { - chainsi = unshare_strinfo (chainsi); - if (chainsi->first == 0) - chainsi->first = chainsi->idx; - chainsi->next = idx; - if (chainsi->endptr == NULL_TREE) - chainsi->endptr = ptr; - si->prev = chainsi->idx; - si->first = chainsi->first; - si->writable = chainsi->writable; - } - return si; -} - -/* For strinfo ORIGSI whose length has been just updated, adjust other - related strinfos so that they match the new ORIGSI. This involves: - - - adding ADJ to the nonzero_chars fields - - copying full_string_p from the new ORIGSI. */ - -static void -adjust_related_strinfos (location_t loc, strinfo *origsi, tree adj) -{ - strinfo *si = verify_related_strinfos (origsi); - - if (si == NULL) - return; - - while (1) - { - strinfo *nsi; - - if (si != origsi) - { - tree tem; - - si = unshare_strinfo (si); - /* We shouldn't see delayed lengths here; the caller must - have calculated the old length in order to calculate - the adjustment. */ - gcc_assert (si->nonzero_chars); - tem = fold_convert_loc (loc, TREE_TYPE (si->nonzero_chars), adj); - si->nonzero_chars = fold_build2_loc (loc, PLUS_EXPR, - TREE_TYPE (si->nonzero_chars), - si->nonzero_chars, tem); - si->full_string_p = origsi->full_string_p; - - si->endptr = NULL_TREE; - si->dont_invalidate = true; - } - nsi = get_next_strinfo (si); - if (nsi == NULL) - return; - si = nsi; - } -} - -/* Find if there are other SSA_NAME pointers equal to PTR - for which we don't track their string lengths yet. If so, use - IDX for them. */ - -static void -find_equal_ptrs (tree ptr, int idx) -{ - if (TREE_CODE (ptr) != SSA_NAME) - return; - while (1) - { - gimple *stmt = SSA_NAME_DEF_STMT (ptr); - if (!is_gimple_assign (stmt)) - return; - ptr = gimple_assign_rhs1 (stmt); - switch (gimple_assign_rhs_code (stmt)) - { - case SSA_NAME: - break; - CASE_CONVERT: - if (!POINTER_TYPE_P (TREE_TYPE (ptr))) - return; - if (TREE_CODE (ptr) == SSA_NAME) - break; - if (TREE_CODE (ptr) != ADDR_EXPR) - return; - /* FALLTHRU */ - case ADDR_EXPR: - { - int *pidx = addr_stridxptr (TREE_OPERAND (ptr, 0)); - if (pidx != NULL && *pidx == 0) - *pidx = idx; - return; - } - default: - return; - } - - /* We might find an endptr created in this pass. Grow the - vector in that case. */ - if (ssa_ver_to_stridx.length () <= SSA_NAME_VERSION (ptr)) - ssa_ver_to_stridx.safe_grow_cleared (num_ssa_names, true); - - if (ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)] != 0) - return; - ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)] = idx; - } -} - -/* Return true if STMT is a call to a builtin function with the right - arguments and attributes that should be considered for optimization - by this pass. */ - -static bool -valid_builtin_call (gimple *stmt) -{ - if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) - return false; - - tree callee = gimple_call_fndecl (stmt); - tree decl = builtin_decl_explicit (DECL_FUNCTION_CODE (callee)); - if (decl - && decl != callee - && !gimple_builtin_call_types_compatible_p (stmt, decl)) - return false; - - switch (DECL_FUNCTION_CODE (callee)) - { - case BUILT_IN_MEMCMP: - case BUILT_IN_MEMCMP_EQ: - case BUILT_IN_STRCMP: - case BUILT_IN_STRNCMP: - case BUILT_IN_STRCHR: - case BUILT_IN_STRLEN: - case BUILT_IN_STRNLEN: - /* The above functions should be pure. Punt if they aren't. */ - if (gimple_vdef (stmt) || gimple_vuse (stmt) == NULL_TREE) - return false; - break; - - case BUILT_IN_ALLOCA: - case BUILT_IN_ALLOCA_WITH_ALIGN: - case BUILT_IN_CALLOC: - case BUILT_IN_MALLOC: - case BUILT_IN_MEMCPY: - case BUILT_IN_MEMCPY_CHK: - case BUILT_IN_MEMPCPY: - case BUILT_IN_MEMPCPY_CHK: - case BUILT_IN_MEMSET: - case BUILT_IN_STPCPY: - case BUILT_IN_STPCPY_CHK: - case BUILT_IN_STPNCPY: - case BUILT_IN_STPNCPY_CHK: - 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: - /* The above functions should be neither const nor pure. Punt if they - aren't. */ - if (gimple_vdef (stmt) == NULL_TREE || gimple_vuse (stmt) == NULL_TREE) - return false; - break; - - default: - break; - } - - return true; -} - -/* If the last .MEM setter statement before STMT is - memcpy (x, y, strlen (y) + 1), the only .MEM use of it is STMT - and STMT is known to overwrite x[strlen (x)], adjust the last memcpy to - just memcpy (x, y, strlen (y)). SI must be the zero length - strinfo. */ - -void -strlen_pass::adjust_last_stmt (strinfo *si, gimple *stmt, bool is_strcat) -{ - tree vuse, callee, len; - struct laststmt_struct last = laststmt; - strinfo *lastsi, *firstsi; - unsigned len_arg_no = 2; - - laststmt.stmt = NULL; - laststmt.len = NULL_TREE; - laststmt.stridx = 0; - - if (last.stmt == NULL) - return; - - vuse = gimple_vuse (stmt); - if (vuse == NULL_TREE - || SSA_NAME_DEF_STMT (vuse) != last.stmt - || !has_single_use (vuse)) - return; - - gcc_assert (last.stridx > 0); - lastsi = get_strinfo (last.stridx); - if (lastsi == NULL) - return; - - if (lastsi != si) - { - if (lastsi->first == 0 || lastsi->first != si->first) - return; - - firstsi = verify_related_strinfos (si); - if (firstsi == NULL) - return; - while (firstsi != lastsi) - { - firstsi = get_next_strinfo (firstsi); - if (firstsi == NULL) - return; - } - } - - if (!is_strcat && !zero_length_string_p (si)) - return; - - if (is_gimple_assign (last.stmt)) - { - gimple_stmt_iterator gsi; - - if (!integer_zerop (gimple_assign_rhs1 (last.stmt))) - return; - if (stmt_could_throw_p (cfun, last.stmt)) - return; - gsi = gsi_for_stmt (last.stmt); - unlink_stmt_vdef (last.stmt); - release_defs (last.stmt); - gsi_remove (&gsi, true); - return; - } - - if (!valid_builtin_call (last.stmt)) - return; - - callee = gimple_call_fndecl (last.stmt); - switch (DECL_FUNCTION_CODE (callee)) - { - case BUILT_IN_MEMCPY: - case BUILT_IN_MEMCPY_CHK: - break; - default: - return; - } - - len = gimple_call_arg (last.stmt, len_arg_no); - if (tree_fits_uhwi_p (len)) - { - if (!tree_fits_uhwi_p (last.len) - || integer_zerop (len) - || tree_to_uhwi (len) != tree_to_uhwi (last.len) + 1) - return; - /* Don't adjust the length if it is divisible by 4, it is more efficient - to store the extra '\0' in that case. */ - if ((tree_to_uhwi (len) & 3) == 0) - return; - - /* Don't fold away an out of bounds access, as this defeats proper - warnings. */ - tree dst = gimple_call_arg (last.stmt, 0); - - access_ref aref; - tree size = compute_objsize (dst, stmt, 1, &aref, &ptr_qry); - if (size && tree_int_cst_lt (size, len)) - return; - } - else if (TREE_CODE (len) == SSA_NAME) - { - gimple *def_stmt = SSA_NAME_DEF_STMT (len); - if (!is_gimple_assign (def_stmt) - || gimple_assign_rhs_code (def_stmt) != PLUS_EXPR - || gimple_assign_rhs1 (def_stmt) != last.len - || !integer_onep (gimple_assign_rhs2 (def_stmt))) - return; - } - else - return; - - gimple_call_set_arg (last.stmt, len_arg_no, last.len); - update_stmt (last.stmt); -} - -/* For an LHS that is an SSA_NAME that is the result of a strlen() - call, or when BOUND is non-null, of a strnlen() call, set LHS - range info to [0, min (MAX, BOUND)] when the range includes more - than one value and return LHS. Otherwise, when the range - [MIN, MAX] is such that MIN == MAX, return the tree representation - of (MIN). The latter allows callers to fold suitable strnlen() calls - to constants. */ - -tree -set_strlen_range (tree lhs, wide_int min, wide_int max, - tree bound /* = NULL_TREE */) -{ - if (TREE_CODE (lhs) != SSA_NAME - || !INTEGRAL_TYPE_P (TREE_TYPE (lhs))) - return NULL_TREE; - - if (bound) - { - /* For strnlen, adjust MIN and MAX as necessary. If the bound - is less than the size of the array set MAX to it. It it's - greater than MAX and MAX is non-zero bump MAX down to account - for the necessary terminating nul. Otherwise leave it alone. */ - if (TREE_CODE (bound) == INTEGER_CST) - { - wide_int wibnd = wi::to_wide (bound); - int cmp = wi::cmpu (wibnd, max); - if (cmp < 0) - max = wibnd; - else if (cmp && wi::ne_p (max, min)) - --max; - } - else if (TREE_CODE (bound) == SSA_NAME) - { - value_range r; - get_range_query (cfun)->range_of_expr (r, bound); - if (!r.undefined_p ()) - { - /* For a bound in a known range, adjust the range determined - above as necessary. For a bound in some anti-range or - in an unknown range, use the range determined by callers. */ - if (wi::ltu_p (r.lower_bound (), min)) - min = r.lower_bound (); - if (wi::ltu_p (r.upper_bound (), max)) - max = r.upper_bound (); - } - } - } - - if (min == max) - return wide_int_to_tree (size_type_node, min); - - set_range_info (lhs, VR_RANGE, min, max); - return lhs; -} - -/* For an LHS that is an SSA_NAME and for strlen() or strnlen() argument - SRC, set LHS range info to [0, min (N, BOUND)] if SRC refers to - a character array A[N] with unknown length bounded by N, and for - strnlen(), by min (N, BOUND). */ - -static tree -maybe_set_strlen_range (tree lhs, tree src, tree bound) -{ - if (TREE_CODE (lhs) != SSA_NAME - || !INTEGRAL_TYPE_P (TREE_TYPE (lhs))) - return NULL_TREE; - - if (TREE_CODE (src) == SSA_NAME) - { - gimple *def = SSA_NAME_DEF_STMT (src); - if (is_gimple_assign (def) - && gimple_assign_rhs_code (def) == ADDR_EXPR) - src = gimple_assign_rhs1 (def); - } - - /* The longest string is PTRDIFF_MAX - 1 bytes including the final - NUL so that the difference between a pointer to just past it and - one to its beginning is positive. */ - wide_int max = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2; - - if (TREE_CODE (src) == ADDR_EXPR) - { - /* The last array member of a struct can be bigger than its size - suggests if it's treated as a poor-man's flexible array member. */ - src = TREE_OPERAND (src, 0); - if (TREE_CODE (src) != MEM_REF - && !array_at_struct_end_p (src)) - { - tree type = TREE_TYPE (src); - tree size = TYPE_SIZE_UNIT (type); - if (size - && TREE_CODE (size) == INTEGER_CST - && !integer_zerop (size)) - { - /* Even though such uses of strlen would be undefined, - avoid relying on arrays of arrays in case some genius - decides to call strlen on an unterminated array element - that's followed by a terminated one. Likewise, avoid - assuming that a struct array member is necessarily - nul-terminated (the nul may be in the member that - follows). In those cases, assume that the length - of the string stored in such an array is bounded - by the size of the enclosing object if one can be - determined. */ - tree base = get_base_address (src); - if (VAR_P (base)) - { - if (tree size = DECL_SIZE_UNIT (base)) - if (size - && TREE_CODE (size) == INTEGER_CST - && TREE_CODE (TREE_TYPE (base)) != POINTER_TYPE) - max = wi::to_wide (size); - } - } - - /* For strlen() the upper bound above is equal to - the longest string that can be stored in the array - (i.e., it accounts for the terminating nul. For - strnlen() bump up the maximum by one since the array - need not be nul-terminated. */ - if (!bound && max != 0) - --max; - } - } - - wide_int min = wi::zero (max.get_precision ()); - return set_strlen_range (lhs, min, max, bound); -} - -/* Diagnose buffer overflow by a STMT writing LEN + PLUS_ONE bytes, - either into a region allocated for the object SI when non-null, - or into an object designated by the LHS of STMT otherwise. - For a call STMT, when CALL_LHS is set use its left hand side - as the destination, otherwise use argument zero. - When nonnull uses RVALS to determine range information. - RAWMEM may be set by memcpy and other raw memory functions - to allow accesses across subobject boundaries. */ - -void -strlen_pass::maybe_warn_overflow (gimple *stmt, bool call_lhs, tree len, - strinfo *si, bool plus_one, bool rawmem) -{ - if (!len || warning_suppressed_p (stmt, OPT_Wstringop_overflow_)) - return; - - /* The DECL of the function performing the write if it is done - by one. */ - tree writefn = NULL_TREE; - /* The destination expression involved in the store or call STMT. */ - tree dest = NULL_TREE; - - if (is_gimple_assign (stmt)) - dest = gimple_assign_lhs (stmt); - else if (is_gimple_call (stmt)) - { - if (call_lhs) - dest = gimple_call_lhs (stmt); - else - { - gcc_assert (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)); - dest = gimple_call_arg (stmt, 0); - } - - if (!dest) - return; - writefn = gimple_call_fndecl (stmt); - } - else - return; - - if (warning_suppressed_p (dest, OPT_Wstringop_overflow_)) - return; - - const int ostype = rawmem ? 0 : 1; - - /* Use maximum precision to avoid overflow in the addition below. - Make sure all operands have the same precision to keep wide_int - from ICE'ing. */ - - access_ref aref; - /* The size of the destination region (which is smaller than - the destination object for stores at a non-zero offset). */ - tree destsize = compute_objsize (dest, stmt, ostype, &aref, &ptr_qry); - - if (!destsize) - { - aref.sizrng[0] = 0; - aref.sizrng[1] = wi::to_offset (max_object_size ()); - } - - /* Return early if the DESTSIZE size expression is the same as LEN - and the offset into the destination is zero. This might happen - in the case of a pair of malloc and memset calls to allocate - an object and clear it as if by calloc. */ - if (destsize == len && !plus_one - && aref.offrng[0] == 0 && aref.offrng[0] == aref.offrng[1]) - return; - - wide_int rng[2]; - if (!get_range (len, stmt, rng, ptr_qry.rvals)) - return; - - widest_int lenrng[2] = - { widest_int::from (rng[0], SIGNED), widest_int::from (rng[1], SIGNED) }; - - if (plus_one) - { - lenrng[0] += 1; - lenrng[1] += 1; - } - - /* The size of the remaining space in the destination computed - as the size of the latter minus the offset into it. */ - widest_int spcrng[2]; - { - offset_int remrng[2]; - remrng[1] = aref.size_remaining (remrng); - spcrng[0] = remrng[0] == -1 ? 0 : widest_int::from (remrng[0], UNSIGNED); - spcrng[1] = widest_int::from (remrng[1], UNSIGNED); - } - - if (wi::leu_p (lenrng[0], spcrng[0]) - && wi::leu_p (lenrng[1], spcrng[1])) - return; - - location_t loc = gimple_or_expr_nonartificial_location (stmt, dest); - bool warned = false; - if (wi::leu_p (lenrng[0], spcrng[1])) - { - if (len != destsize - && (!si || rawmem || !is_strlen_related_p (si->ptr, len))) - return; - - warned = (writefn - ? warning_at (loc, OPT_Wstringop_overflow_, - "%qD writing one too many bytes into a region " - "of a size that depends on %<strlen%>", - writefn) - : warning_at (loc, OPT_Wstringop_overflow_, - "writing one too many bytes into a region " - "of a size that depends on %<strlen%>")); - } - else if (lenrng[0] == lenrng[1]) - { - if (spcrng[0] == spcrng[1]) - warned = (writefn - ? warning_n (loc, OPT_Wstringop_overflow_, - lenrng[0].to_uhwi (), - "%qD writing %wu byte into a region " - "of size %wu", - "%qD writing %wu bytes into a region " - "of size %wu", - writefn, lenrng[0].to_uhwi (), - spcrng[0].to_uhwi ()) - : warning_n (loc, OPT_Wstringop_overflow_, - lenrng[0].to_uhwi (), - "writing %wu byte into a region " - "of size %wu", - "writing %wu bytes into a region " - "of size %wu", - lenrng[0].to_uhwi (), - spcrng[0].to_uhwi ())); - else - warned = (writefn - ? warning_n (loc, OPT_Wstringop_overflow_, - lenrng[0].to_uhwi (), - "%qD writing %wu byte into a region " - "of size between %wu and %wu", - "%qD writing %wu bytes into a region " - "of size between %wu and %wu", - writefn, lenrng[0].to_uhwi (), - spcrng[0].to_uhwi (), spcrng[1].to_uhwi ()) - : warning_n (loc, OPT_Wstringop_overflow_, - lenrng[0].to_uhwi (), - "writing %wu byte into a region " - "of size between %wu and %wu", - "writing %wu bytes into a region " - "of size between %wu and %wu", - lenrng[0].to_uhwi (), - spcrng[0].to_uhwi (), spcrng[1].to_uhwi ())); - } - else if (spcrng[0] == spcrng[1]) - warned = (writefn - ? warning_at (loc, OPT_Wstringop_overflow_, - "%qD writing between %wu and %wu bytes " - "into a region of size %wu", - writefn, lenrng[0].to_uhwi (), - lenrng[1].to_uhwi (), - spcrng[0].to_uhwi ()) - : warning_at (loc, OPT_Wstringop_overflow_, - "writing between %wu and %wu bytes " - "into a region of size %wu", - lenrng[0].to_uhwi (), - lenrng[1].to_uhwi (), - spcrng[0].to_uhwi ())); - else - warned = (writefn - ? warning_at (loc, OPT_Wstringop_overflow_, - "%qD writing between %wu and %wu bytes " - "into a region of size between %wu and %wu", - writefn, lenrng[0].to_uhwi (), - lenrng[1].to_uhwi (), - spcrng[0].to_uhwi (), spcrng[1].to_uhwi ()) - : warning_at (loc, OPT_Wstringop_overflow_, - "writing between %wu and %wu bytes " - "into a region of size between %wu and %wu", - lenrng[0].to_uhwi (), - lenrng[1].to_uhwi (), - spcrng[0].to_uhwi (), spcrng[1].to_uhwi ())); - - if (!warned) - return; - - suppress_warning (stmt, OPT_Wstringop_overflow_); - - aref.inform_access (access_write_only); -} - -/* Convenience wrapper for the above. */ - -void -strlen_pass::maybe_warn_overflow (gimple *stmt, bool call_lhs, - unsigned HOST_WIDE_INT len, - strinfo *si, bool plus_one, bool rawmem) -{ - tree tlen = build_int_cst (size_type_node, len); - maybe_warn_overflow (stmt, call_lhs, tlen, si, plus_one, rawmem); -} - -/* Handle a strlen call. If strlen of the argument is known, replace - the strlen call with the known value, otherwise remember that strlen - of the argument is stored in the lhs SSA_NAME. */ - -void -strlen_pass::handle_builtin_strlen () -{ - gimple *stmt = gsi_stmt (m_gsi); - tree lhs = gimple_call_lhs (stmt); - - if (lhs == NULL_TREE) - return; - - location_t loc = gimple_location (stmt); - tree callee = gimple_call_fndecl (stmt); - tree src = gimple_call_arg (stmt, 0); - tree bound = (DECL_FUNCTION_CODE (callee) == BUILT_IN_STRNLEN - ? gimple_call_arg (stmt, 1) : NULL_TREE); - int idx = get_stridx (src); - if (idx || (bound && integer_zerop (bound))) - { - strinfo *si = NULL; - tree rhs; - - if (idx < 0) - rhs = build_int_cst (TREE_TYPE (lhs), ~idx); - else if (idx == 0) - rhs = bound; - else - { - rhs = NULL_TREE; - si = get_strinfo (idx); - if (si != NULL) - { - rhs = get_string_length (si); - /* For strnlen, if bound is constant, even if si is not known - to be zero terminated, if we know at least bound bytes are - not zero, the return value will be bound. */ - if (rhs == NULL_TREE - && bound != NULL_TREE - && TREE_CODE (bound) == INTEGER_CST - && si->nonzero_chars != NULL_TREE - && TREE_CODE (si->nonzero_chars) == INTEGER_CST - && tree_int_cst_le (bound, si->nonzero_chars)) - rhs = bound; - } - } - if (rhs != NULL_TREE) - { - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "Optimizing: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - rhs = unshare_expr (rhs); - if (!useless_type_conversion_p (TREE_TYPE (lhs), TREE_TYPE (rhs))) - rhs = fold_convert_loc (loc, TREE_TYPE (lhs), rhs); - - if (bound) - rhs = fold_build2_loc (loc, MIN_EXPR, TREE_TYPE (rhs), rhs, bound); - - gimplify_and_update_call_from_tree (&m_gsi, rhs); - stmt = gsi_stmt (m_gsi); - update_stmt (stmt); - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "into: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - - if (si != NULL - /* Don't update anything for strnlen. */ - && bound == NULL_TREE - && TREE_CODE (si->nonzero_chars) != SSA_NAME - && TREE_CODE (si->nonzero_chars) != INTEGER_CST - && !SSA_NAME_OCCURS_IN_ABNORMAL_PHI (lhs)) - { - si = unshare_strinfo (si); - si->nonzero_chars = lhs; - gcc_assert (si->full_string_p); - } - - if (strlen_to_stridx - && (bound == NULL_TREE - /* For strnlen record this only if the call is proven - to return the same value as strlen would. */ - || (TREE_CODE (bound) == INTEGER_CST - && TREE_CODE (rhs) == INTEGER_CST - && tree_int_cst_lt (rhs, bound)))) - strlen_to_stridx->put (lhs, stridx_strlenloc (idx, loc)); - - return; - } - } - if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (lhs)) - return; - - if (idx == 0) - idx = new_stridx (src); - else - { - strinfo *si = get_strinfo (idx); - if (si != NULL) - { - if (!si->full_string_p && !si->stmt) - { - /* Until now we only had a lower bound on the string length. - Install LHS as the actual length. */ - si = unshare_strinfo (si); - tree old = si->nonzero_chars; - si->nonzero_chars = lhs; - si->full_string_p = true; - if (old && TREE_CODE (old) == INTEGER_CST) - { - old = fold_convert_loc (loc, TREE_TYPE (lhs), old); - tree adj = fold_build2_loc (loc, MINUS_EXPR, - TREE_TYPE (lhs), lhs, old); - adjust_related_strinfos (loc, si, adj); - /* Use the constant minimum length as the lower bound - of the non-constant length. */ - wide_int min = wi::to_wide (old); - wide_int max - = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2; - set_strlen_range (lhs, min, max); - } - else - { - si->first = 0; - si->prev = 0; - si->next = 0; - } - } - return; - } - } - if (idx) - { - if (!bound) - { - /* Only store the new length information for calls to strlen(), - not for those to strnlen(). */ - strinfo *si = new_strinfo (src, idx, lhs, true); - set_strinfo (idx, si); - find_equal_ptrs (src, idx); - } - - /* For SRC that is an array of N elements, set LHS's range - to [0, min (N, BOUND)]. A constant return value means - the range would have consisted of a single value. In - that case, fold the result into the returned constant. */ - if (tree ret = maybe_set_strlen_range (lhs, src, bound)) - if (TREE_CODE (ret) == INTEGER_CST) - { - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "Optimizing: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - if (!useless_type_conversion_p (TREE_TYPE (lhs), TREE_TYPE (ret))) - ret = fold_convert_loc (loc, TREE_TYPE (lhs), ret); - gimplify_and_update_call_from_tree (&m_gsi, ret); - stmt = gsi_stmt (m_gsi); - update_stmt (stmt); - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "into: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - } - - if (strlen_to_stridx && !bound) - strlen_to_stridx->put (lhs, stridx_strlenloc (idx, loc)); - } -} - -/* Handle a strchr call. If strlen of the first argument is known, replace - the strchr (x, 0) call with the endptr or x + strlen, otherwise remember - that lhs of the call is endptr and strlen of the argument is endptr - x. */ - -void -strlen_pass::handle_builtin_strchr () -{ - gimple *stmt = gsi_stmt (m_gsi); - tree lhs = gimple_call_lhs (stmt); - - if (lhs == NULL_TREE) - return; - - if (!integer_zerop (gimple_call_arg (stmt, 1))) - return; - - tree src = gimple_call_arg (stmt, 0); - - /* Avoid folding if the first argument is not a nul-terminated array. - Defer warning until later. */ - if (!check_nul_terminated_array (NULL_TREE, src)) - return; - - int idx = get_stridx (src); - if (idx) - { - strinfo *si = NULL; - tree rhs; - - if (idx < 0) - rhs = build_int_cst (size_type_node, ~idx); - else - { - rhs = NULL_TREE; - si = get_strinfo (idx); - if (si != NULL) - rhs = get_string_length (si); - } - if (rhs != NULL_TREE) - { - location_t loc = gimple_location (stmt); - - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "Optimizing: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - if (si != NULL && si->endptr != NULL_TREE) - { - rhs = unshare_expr (si->endptr); - if (!useless_type_conversion_p (TREE_TYPE (lhs), - TREE_TYPE (rhs))) - rhs = fold_convert_loc (loc, TREE_TYPE (lhs), rhs); - } - else - { - rhs = fold_convert_loc (loc, sizetype, unshare_expr (rhs)); - rhs = fold_build2_loc (loc, POINTER_PLUS_EXPR, - TREE_TYPE (src), src, rhs); - if (!useless_type_conversion_p (TREE_TYPE (lhs), - TREE_TYPE (rhs))) - rhs = fold_convert_loc (loc, TREE_TYPE (lhs), rhs); - } - gimplify_and_update_call_from_tree (&m_gsi, rhs); - stmt = gsi_stmt (m_gsi); - update_stmt (stmt); - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "into: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - if (si != NULL - && si->endptr == NULL_TREE - && !SSA_NAME_OCCURS_IN_ABNORMAL_PHI (lhs)) - { - si = unshare_strinfo (si); - si->endptr = lhs; - } - zero_length_string (lhs, si); - return; - } - } - if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (lhs)) - return; - if (TREE_CODE (src) != SSA_NAME || !SSA_NAME_OCCURS_IN_ABNORMAL_PHI (src)) - { - if (idx == 0) - idx = new_stridx (src); - else if (get_strinfo (idx) != NULL) - { - zero_length_string (lhs, NULL); - return; - } - if (idx) - { - location_t loc = gimple_location (stmt); - tree lhsu = fold_convert_loc (loc, size_type_node, lhs); - tree srcu = fold_convert_loc (loc, size_type_node, src); - tree length = fold_build2_loc (loc, MINUS_EXPR, - size_type_node, lhsu, srcu); - strinfo *si = new_strinfo (src, idx, length, true); - si->endptr = lhs; - set_strinfo (idx, si); - find_equal_ptrs (src, idx); - zero_length_string (lhs, si); - } - } - else - zero_length_string (lhs, NULL); -} - -/* Handle a strcpy-like ({st{r,p}cpy,__st{r,p}cpy_chk}) call. - If strlen of the second argument is known, strlen of the first argument - is the same after this call. Furthermore, attempt to convert it to - memcpy. Uses RVALS to determine range information. */ - -void -strlen_pass::handle_builtin_strcpy (built_in_function bcode) -{ - int idx, didx; - tree src, dst, srclen, len, lhs, type, fn, oldlen; - bool success; - gimple *stmt = gsi_stmt (m_gsi); - strinfo *si, *dsi, *olddsi, *zsi; - location_t loc; - - src = gimple_call_arg (stmt, 1); - dst = gimple_call_arg (stmt, 0); - lhs = gimple_call_lhs (stmt); - idx = get_stridx (src); - si = NULL; - if (idx > 0) - si = get_strinfo (idx); - - didx = get_stridx (dst); - olddsi = NULL; - oldlen = NULL_TREE; - if (didx > 0) - olddsi = get_strinfo (didx); - else if (didx < 0) - return; - - if (olddsi != NULL) - adjust_last_stmt (olddsi, stmt, false); - - srclen = NULL_TREE; - if (si != NULL) - srclen = get_string_length (si); - else if (idx < 0) - srclen = build_int_cst (size_type_node, ~idx); - - maybe_warn_overflow (stmt, false, srclen, olddsi, true); - - if (olddsi != NULL) - adjust_last_stmt (olddsi, stmt, false); - - loc = gimple_location (stmt); - if (srclen == NULL_TREE) - switch (bcode) - { - case BUILT_IN_STRCPY: - case BUILT_IN_STRCPY_CHK: - if (lhs != NULL_TREE || !builtin_decl_implicit_p (BUILT_IN_STPCPY)) - return; - break; - case BUILT_IN_STPCPY: - case BUILT_IN_STPCPY_CHK: - if (lhs == NULL_TREE) - return; - else - { - tree lhsuint = fold_convert_loc (loc, size_type_node, lhs); - srclen = fold_convert_loc (loc, size_type_node, dst); - srclen = fold_build2_loc (loc, MINUS_EXPR, size_type_node, - lhsuint, srclen); - } - break; - default: - gcc_unreachable (); - } - - if (didx == 0) - { - didx = new_stridx (dst); - if (didx == 0) - return; - } - if (olddsi != NULL) - { - oldlen = olddsi->nonzero_chars; - dsi = unshare_strinfo (olddsi); - dsi->nonzero_chars = srclen; - dsi->full_string_p = (srclen != NULL_TREE); - /* Break the chain, so adjust_related_strinfo on later pointers in - the chain won't adjust this one anymore. */ - dsi->next = 0; - dsi->stmt = NULL; - dsi->endptr = NULL_TREE; - } - else - { - dsi = new_strinfo (dst, didx, srclen, srclen != NULL_TREE); - set_strinfo (didx, dsi); - find_equal_ptrs (dst, didx); - } - dsi->writable = true; - dsi->dont_invalidate = true; - - if (dsi->nonzero_chars == NULL_TREE) - { - strinfo *chainsi; - - /* If string length of src is unknown, use delayed length - computation. If string length of dst will be needed, it - can be computed by transforming this strcpy call into - stpcpy and subtracting dst from the return value. */ - - /* Look for earlier strings whose length could be determined if - this strcpy is turned into an stpcpy. */ - - if (dsi->prev != 0 && (chainsi = verify_related_strinfos (dsi)) != NULL) - { - for (; chainsi && chainsi != dsi; chainsi = get_strinfo (chainsi->next)) - { - /* When setting a stmt for delayed length computation - prevent all strinfos through dsi from being - invalidated. */ - chainsi = unshare_strinfo (chainsi); - chainsi->stmt = stmt; - chainsi->nonzero_chars = NULL_TREE; - chainsi->full_string_p = false; - chainsi->endptr = NULL_TREE; - chainsi->dont_invalidate = true; - } - } - dsi->stmt = stmt; - - /* Try to detect overlap before returning. This catches cases - like strcpy (d, d + n) where n is non-constant whose range - is such that (n <= strlen (d) holds). - - OLDDSI->NONZERO_chars may have been reset by this point with - oldlen holding it original value. */ - if (olddsi && oldlen) - { - /* Add 1 for the terminating NUL. */ - tree type = TREE_TYPE (oldlen); - oldlen = fold_build2 (PLUS_EXPR, type, oldlen, - build_int_cst (type, 1)); - check_bounds_or_overlap (stmt, olddsi->ptr, src, oldlen, NULL_TREE); - } - - return; - } - - if (olddsi != NULL) - { - tree adj = NULL_TREE; - if (oldlen == NULL_TREE) - ; - else if (integer_zerop (oldlen)) - adj = srclen; - else if (TREE_CODE (oldlen) == INTEGER_CST - || TREE_CODE (srclen) == INTEGER_CST) - adj = fold_build2_loc (loc, MINUS_EXPR, - TREE_TYPE (srclen), srclen, - fold_convert_loc (loc, TREE_TYPE (srclen), - oldlen)); - if (adj != NULL_TREE) - adjust_related_strinfos (loc, dsi, adj); - else - dsi->prev = 0; - } - /* strcpy src may not overlap dst, so src doesn't need to be - invalidated either. */ - if (si != NULL) - si->dont_invalidate = true; - - fn = NULL_TREE; - zsi = NULL; - switch (bcode) - { - case BUILT_IN_STRCPY: - fn = builtin_decl_implicit (BUILT_IN_MEMCPY); - if (lhs) - ssa_ver_to_stridx[SSA_NAME_VERSION (lhs)] = didx; - break; - case BUILT_IN_STRCPY_CHK: - fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK); - if (lhs) - ssa_ver_to_stridx[SSA_NAME_VERSION (lhs)] = didx; - break; - case BUILT_IN_STPCPY: - /* This would need adjustment of the lhs (subtract one), - or detection that the trailing '\0' doesn't need to be - written, if it will be immediately overwritten. - fn = builtin_decl_explicit (BUILT_IN_MEMPCPY); */ - if (lhs) - { - dsi->endptr = lhs; - zsi = zero_length_string (lhs, dsi); - } - break; - case BUILT_IN_STPCPY_CHK: - /* This would need adjustment of the lhs (subtract one), - or detection that the trailing '\0' doesn't need to be - written, if it will be immediately overwritten. - fn = builtin_decl_explicit (BUILT_IN_MEMPCPY_CHK); */ - if (lhs) - { - dsi->endptr = lhs; - zsi = zero_length_string (lhs, dsi); - } - break; - default: - gcc_unreachable (); - } - if (zsi != NULL) - zsi->dont_invalidate = true; - - if (fn) - { - tree args = TYPE_ARG_TYPES (TREE_TYPE (fn)); - type = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args))); - } - else - type = size_type_node; - - len = fold_convert_loc (loc, type, unshare_expr (srclen)); - len = fold_build2_loc (loc, PLUS_EXPR, type, len, build_int_cst (type, 1)); - - /* Disable warning for the transformed statement? */ - opt_code no_warning_opt = no_warning; - - if (const strinfo *chksi = si ? olddsi ? olddsi : dsi : NULL) - { - no_warning_opt = check_bounds_or_overlap (stmt, chksi->ptr, si->ptr, - NULL_TREE, len); - if (no_warning_opt) - suppress_warning (stmt, no_warning_opt); - } - - if (fn == NULL_TREE) - return; - - len = force_gimple_operand_gsi (&m_gsi, len, true, NULL_TREE, true, - GSI_SAME_STMT); - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "Optimizing: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - if (gimple_call_num_args (stmt) == 2) - success = update_gimple_call (&m_gsi, fn, 3, dst, src, len); - else - success = update_gimple_call (&m_gsi, fn, 4, dst, src, len, - gimple_call_arg (stmt, 2)); - if (success) - { - stmt = gsi_stmt (m_gsi); - update_stmt (stmt); - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "into: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - /* Allow adjust_last_stmt to decrease this memcpy's size. */ - laststmt.stmt = stmt; - laststmt.len = srclen; - laststmt.stridx = dsi->idx; - } - else if (dump_file && (dump_flags & TDF_DETAILS) != 0) - fprintf (dump_file, "not possible.\n"); - - if (no_warning_opt) - suppress_warning (stmt, no_warning_opt); -} - -/* Check the size argument to the built-in forms of stpncpy and strncpy - for out-of-bounds offsets or overlapping access, and to see if the - size argument is derived from a call to strlen() on the source argument, - and if so, issue an appropriate warning. */ - -void -strlen_pass::handle_builtin_strncat (built_in_function) -{ - /* Same as stxncpy(). */ - handle_builtin_stxncpy_strncat (true); -} - -/* Return true if LEN depends on a call to strlen(SRC) in an interesting - way. LEN can either be an integer expression, or a pointer (to char). - When it is the latter (such as in recursive calls to self) it is - assumed to be the argument in some call to strlen() whose relationship - to SRC is being ascertained. */ - -bool -is_strlen_related_p (tree src, tree len) -{ - if (TREE_CODE (TREE_TYPE (len)) == POINTER_TYPE - && operand_equal_p (src, len, 0)) - return true; - - if (TREE_CODE (len) != SSA_NAME) - return false; - - if (TREE_CODE (src) == SSA_NAME) - { - gimple *srcdef = SSA_NAME_DEF_STMT (src); - if (is_gimple_assign (srcdef)) - { - /* Handle bitwise AND used in conversions from wider size_t - to narrower unsigned types. */ - tree_code code = gimple_assign_rhs_code (srcdef); - if (code == BIT_AND_EXPR - || code == NOP_EXPR) - return is_strlen_related_p (gimple_assign_rhs1 (srcdef), len); - - return false; - } - - if (gimple_call_builtin_p (srcdef, BUILT_IN_NORMAL)) - { - /* If SRC is the result of a call to an allocation function - or strlen, use the function's argument instead. */ - tree func = gimple_call_fndecl (srcdef); - built_in_function code = DECL_FUNCTION_CODE (func); - if (code == BUILT_IN_ALLOCA - || code == BUILT_IN_ALLOCA_WITH_ALIGN - || code == BUILT_IN_MALLOC - || code == BUILT_IN_STRLEN) - return is_strlen_related_p (gimple_call_arg (srcdef, 0), len); - - /* FIXME: Handle other functions with attribute alloc_size. */ - return false; - } - } - - gimple *lendef = SSA_NAME_DEF_STMT (len); - if (!lendef) - return false; - - if (is_gimple_call (lendef)) - { - tree func = gimple_call_fndecl (lendef); - if (!valid_builtin_call (lendef) - || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN) - return false; - - tree arg = gimple_call_arg (lendef, 0); - return is_strlen_related_p (src, arg); - } - - if (!is_gimple_assign (lendef)) - return false; - - tree_code code = gimple_assign_rhs_code (lendef); - tree rhs1 = gimple_assign_rhs1 (lendef); - tree rhstype = TREE_TYPE (rhs1); - - if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR) - || (INTEGRAL_TYPE_P (rhstype) - && (code == BIT_AND_EXPR - || code == NOP_EXPR))) - { - /* Pointer plus (an integer), and truncation are considered among - the (potentially) related expressions to strlen. */ - return is_strlen_related_p (src, rhs1); - } - - if (tree rhs2 = gimple_assign_rhs2 (lendef)) - { - /* Integer subtraction is considered strlen-related when both - arguments are integers and second one is strlen-related. */ - rhstype = TREE_TYPE (rhs2); - if (INTEGRAL_TYPE_P (rhstype) && code == MINUS_EXPR) - return is_strlen_related_p (src, rhs2); - } - - return false; -} - -/* Called by handle_builtin_stxncpy_strncat and by - gimple_fold_builtin_strncpy in gimple-fold.c. - Check to see if the specified bound is a) equal to the size of - the destination DST and if so, b) if it's immediately followed by - DST[CNT - 1] = '\0'. If a) holds and b) does not, warn. Otherwise, - do nothing. Return true if diagnostic has been issued. - - The purpose is to diagnose calls to strncpy and stpncpy that do - not nul-terminate the copy while allowing for the idiom where - such a call is immediately followed by setting the last element - to nul, as in: - char a[32]; - strncpy (a, s, sizeof a); - a[sizeof a - 1] = '\0'; -*/ - -bool -maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt, - pointer_query *ptr_qry /* = NULL */) -{ - gimple *stmt = gsi_stmt (gsi); - if (warning_suppressed_p (stmt, OPT_Wstringop_truncation)) - return false; - - wide_int cntrange[2]; - value_range r; - if (!get_range_query (cfun)->range_of_expr (r, cnt) - || r.varying_p () - || r.undefined_p ()) - return false; - - cntrange[0] = wi::to_wide (r.min ()); - cntrange[1] = wi::to_wide (r.max ()); - if (r.kind () == VR_ANTI_RANGE) - { - wide_int maxobjsize = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)); - - if (wi::ltu_p (cntrange[1], maxobjsize)) - { - cntrange[0] = cntrange[1] + 1; - cntrange[1] = maxobjsize; - } - else - { - cntrange[1] = cntrange[0] - 1; - cntrange[0] = wi::zero (TYPE_PRECISION (TREE_TYPE (cnt))); - } - } - - /* Negative value is the constant string length. If it's less than - the lower bound there is no truncation. Avoid calling get_stridx() - when ssa_ver_to_stridx is empty. That implies the caller isn't - running under the control of this pass and ssa_ver_to_stridx hasn't - been created yet. */ - int sidx = ssa_ver_to_stridx.length () ? get_stridx (src) : 0; - if (sidx < 0 && wi::gtu_p (cntrange[0], ~sidx)) - return false; - - tree dst = gimple_call_arg (stmt, 0); - tree dstdecl = dst; - if (TREE_CODE (dstdecl) == ADDR_EXPR) - dstdecl = TREE_OPERAND (dstdecl, 0); - - tree ref = NULL_TREE; - - if (!sidx) - { - /* If the source is a non-string return early to avoid warning - for possible truncation (if the truncation is certain SIDX - is non-zero). */ - tree srcdecl = gimple_call_arg (stmt, 1); - if (TREE_CODE (srcdecl) == ADDR_EXPR) - srcdecl = TREE_OPERAND (srcdecl, 0); - if (get_attr_nonstring_decl (srcdecl, &ref)) - return false; - } - - /* Likewise, if the destination refers to an array/pointer declared - nonstring return early. */ - if (get_attr_nonstring_decl (dstdecl, &ref)) - return false; - - /* Look for dst[i] = '\0'; after the stxncpy() call and if found - avoid the truncation warning. */ - gsi_next_nondebug (&gsi); - gimple *next_stmt = gsi_stmt (gsi); - if (!next_stmt) - { - /* When there is no statement in the same basic block check - the immediate successor block. */ - if (basic_block bb = gimple_bb (stmt)) - { - if (single_succ_p (bb)) - { - /* For simplicity, ignore blocks with multiple outgoing - edges for now and only consider successor blocks along - normal edges. */ - edge e = EDGE_SUCC (bb, 0); - if (!(e->flags & EDGE_ABNORMAL)) - { - gsi = gsi_start_bb (e->dest); - next_stmt = gsi_stmt (gsi); - if (next_stmt && is_gimple_debug (next_stmt)) - { - gsi_next_nondebug (&gsi); - next_stmt = gsi_stmt (gsi); - } - } - } - } - } - - if (next_stmt && is_gimple_assign (next_stmt)) - { - tree lhs = gimple_assign_lhs (next_stmt); - tree_code code = TREE_CODE (lhs); - if (code == ARRAY_REF || code == MEM_REF) - lhs = TREE_OPERAND (lhs, 0); - - tree func = gimple_call_fndecl (stmt); - if (DECL_FUNCTION_CODE (func) == BUILT_IN_STPNCPY) - { - tree ret = gimple_call_lhs (stmt); - if (ret && operand_equal_p (ret, lhs, 0)) - return false; - } - - /* Determine the base address and offset of the reference, - ignoring the innermost array index. */ - if (TREE_CODE (ref) == ARRAY_REF) - ref = TREE_OPERAND (ref, 0); - - poly_int64 dstoff; - tree dstbase = get_addr_base_and_unit_offset (ref, &dstoff); - - poly_int64 lhsoff; - tree lhsbase = get_addr_base_and_unit_offset (lhs, &lhsoff); - if (lhsbase - && dstbase - && known_eq (dstoff, lhsoff) - && operand_equal_p (dstbase, lhsbase, 0)) - return false; - } - - int prec = TYPE_PRECISION (TREE_TYPE (cnt)); - wide_int lenrange[2]; - if (strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL) - { - lenrange[0] = (sisrc->nonzero_chars - && TREE_CODE (sisrc->nonzero_chars) == INTEGER_CST - ? wi::to_wide (sisrc->nonzero_chars) - : wi::zero (prec)); - lenrange[1] = lenrange[0]; - } - else if (sidx < 0) - lenrange[0] = lenrange[1] = wi::shwi (~sidx, prec); - else - { - c_strlen_data lendata = { }; - /* Set MAXBOUND to an arbitrary non-null non-integer node as a request - to have it set to the length of the longest string in a PHI. */ - lendata.maxbound = src; - get_range_strlen (src, &lendata, /* eltsize = */1); - if (TREE_CODE (lendata.minlen) == INTEGER_CST - && TREE_CODE (lendata.maxbound) == INTEGER_CST) - { - /* When LENDATA.MAXLEN is unknown, reset LENDATA.MINLEN - which stores the length of the shortest known string. */ - if (integer_all_onesp (lendata.maxlen)) - lenrange[0] = wi::shwi (0, prec); - else - lenrange[0] = wi::to_wide (lendata.minlen, prec); - lenrange[1] = wi::to_wide (lendata.maxbound, prec); - } - else - { - lenrange[0] = wi::shwi (0, prec); - lenrange[1] = wi::shwi (-1, prec); - } - } - - location_t callloc = gimple_or_expr_nonartificial_location (stmt, dst); - tree func = gimple_call_fndecl (stmt); - - if (lenrange[0] != 0 || !wi::neg_p (lenrange[1])) - { - /* If the longest source string is shorter than the lower bound - of the specified count the copy is definitely nul-terminated. */ - if (wi::ltu_p (lenrange[1], cntrange[0])) - return false; - - if (wi::neg_p (lenrange[1])) - { - /* The length of one of the strings is unknown but at least - one has non-zero length and that length is stored in - LENRANGE[1]. Swap the bounds to force a "may be truncated" - warning below. */ - lenrange[1] = lenrange[0]; - lenrange[0] = wi::shwi (0, prec); - } - - /* Set to true for strncat whose bound is derived from the length - of the destination (the expected usage pattern). */ - bool cat_dstlen_bounded = false; - if (DECL_FUNCTION_CODE (func) == BUILT_IN_STRNCAT) - cat_dstlen_bounded = is_strlen_related_p (dst, cnt); - - if (lenrange[0] == cntrange[1] && cntrange[0] == cntrange[1]) - return warning_n (callloc, OPT_Wstringop_truncation, - cntrange[0].to_uhwi (), - "%qD output truncated before terminating " - "nul copying %E byte from a string of the " - "same length", - "%qD output truncated before terminating nul " - "copying %E bytes from a string of the same " - "length", - func, cnt); - else if (!cat_dstlen_bounded) - { - if (wi::geu_p (lenrange[0], cntrange[1])) - { - /* The shortest string is longer than the upper bound of - the count so the truncation is certain. */ - if (cntrange[0] == cntrange[1]) - return warning_n (callloc, OPT_Wstringop_truncation, - cntrange[0].to_uhwi (), - "%qD output truncated copying %E byte " - "from a string of length %wu", - "%qD output truncated copying %E bytes " - "from a string of length %wu", - func, cnt, lenrange[0].to_uhwi ()); - - return warning_at (callloc, OPT_Wstringop_truncation, - "%qD output truncated copying between %wu " - "and %wu bytes from a string of length %wu", - func, cntrange[0].to_uhwi (), - cntrange[1].to_uhwi (), lenrange[0].to_uhwi ()); - } - else if (wi::geu_p (lenrange[1], cntrange[1])) - { - /* The longest string is longer than the upper bound of - the count so the truncation is possible. */ - if (cntrange[0] == cntrange[1]) - return warning_n (callloc, OPT_Wstringop_truncation, - cntrange[0].to_uhwi (), - "%qD output may be truncated copying %E " - "byte from a string of length %wu", - "%qD output may be truncated copying %E " - "bytes from a string of length %wu", - func, cnt, lenrange[1].to_uhwi ()); - - return warning_at (callloc, OPT_Wstringop_truncation, - "%qD output may be truncated copying between " - "%wu and %wu bytes from a string of length %wu", - func, cntrange[0].to_uhwi (), - cntrange[1].to_uhwi (), lenrange[1].to_uhwi ()); - } - } - - if (!cat_dstlen_bounded - && cntrange[0] != cntrange[1] - && wi::leu_p (cntrange[0], lenrange[0]) - && wi::leu_p (cntrange[1], lenrange[0] + 1)) - { - /* If the source (including the terminating nul) is longer than - the lower bound of the specified count but shorter than the - upper bound the copy may (but need not) be truncated. */ - return warning_at (callloc, OPT_Wstringop_truncation, - "%qD output may be truncated copying between " - "%wu and %wu bytes from a string of length %wu", - func, cntrange[0].to_uhwi (), - cntrange[1].to_uhwi (), lenrange[0].to_uhwi ()); - } - } - - access_ref aref; - if (tree dstsize = compute_objsize (dst, stmt, 1, &aref, ptr_qry)) - { - /* The source length is unknown. Try to determine the destination - size and see if it matches the specified bound. If not, bail. - Otherwise go on to see if it should be diagnosed for possible - truncation. */ - if (!dstsize) - return false; - - if (wi::to_wide (dstsize) != cntrange[1]) - return false; - - /* Avoid warning for strncpy(a, b, N) calls where the following - equalities hold: - N == sizeof a && N == sizeof b */ - if (tree srcsize = compute_objsize (src, stmt, 1, &aref, ptr_qry)) - if (wi::to_wide (srcsize) == cntrange[1]) - return false; - - if (cntrange[0] == cntrange[1]) - return warning_at (callloc, OPT_Wstringop_truncation, - "%qD specified bound %E equals destination size", - func, cnt); - } - - return false; -} - -/* Check the arguments to the built-in forms of stpncpy, strncpy, and - strncat, for out-of-bounds offsets or overlapping access, and to see - if the size is derived from calling strlen() on the source argument, - and if so, issue the appropriate warning. - APPEND_P is true for strncat. */ - -void -strlen_pass::handle_builtin_stxncpy_strncat (bool append_p) -{ - if (!strlen_to_stridx) - return; - - gimple *stmt = gsi_stmt (m_gsi); - - tree dst = gimple_call_arg (stmt, 0); - tree src = gimple_call_arg (stmt, 1); - tree len = gimple_call_arg (stmt, 2); - /* An upper bound of the size of the destination. */ - tree dstsize = NULL_TREE; - /* The length of the destination and source strings (plus 1 for those - whose FULL_STRING_P is set, i.e., whose length is exact rather than - a lower bound). */ - tree dstlenp1 = NULL_TREE, srclenp1 = NULL_TREE;; - - int didx = get_stridx (dst); - if (strinfo *sidst = didx > 0 ? get_strinfo (didx) : NULL) - { - /* Compute the size of the destination string including the nul - if it is known to be nul-terminated. */ - if (sidst->nonzero_chars) - { - if (sidst->full_string_p) - { - /* String is known to be nul-terminated. */ - tree type = TREE_TYPE (sidst->nonzero_chars); - dstlenp1 = fold_build2 (PLUS_EXPR, type, sidst->nonzero_chars, - build_int_cst (type, 1)); - } - else - dstlenp1 = sidst->nonzero_chars; - } - else if (TREE_CODE (sidst->ptr) == SSA_NAME) - { - gimple *def_stmt = SSA_NAME_DEF_STMT (sidst->ptr); - dstsize = gimple_call_alloc_size (def_stmt); - } - - dst = sidst->ptr; - } - - int sidx = get_stridx (src); - strinfo *sisrc = sidx > 0 ? get_strinfo (sidx) : NULL; - if (sisrc) - { - /* strncat() and strncpy() can modify the source string by writing - over the terminating nul so SISRC->DONT_INVALIDATE must be left - clear. */ - - /* Compute the size of the source string including the terminating - nul if its known to be nul-terminated. */ - if (sisrc->nonzero_chars) - { - if (sisrc->full_string_p) - { - tree type = TREE_TYPE (sisrc->nonzero_chars); - srclenp1 = fold_build2 (PLUS_EXPR, type, sisrc->nonzero_chars, - build_int_cst (type, 1)); - } - else - srclenp1 = sisrc->nonzero_chars; - } - - src = sisrc->ptr; - } - else - srclenp1 = NULL_TREE; - - opt_code opt = check_bounds_or_overlap (stmt, dst, src, dstlenp1, srclenp1); - if (opt != no_warning) - { - suppress_warning (stmt, opt); - return; - } - - /* If the length argument was computed from strlen(S) for some string - S retrieve the strinfo index for the string (PSS->FIRST) along with - the location of the strlen() call (PSS->SECOND). */ - stridx_strlenloc *pss = strlen_to_stridx->get (len); - if (!pss || pss->first <= 0) - { - if (maybe_diag_stxncpy_trunc (m_gsi, src, len)) - suppress_warning (stmt, OPT_Wstringop_truncation); - - return; - } - - /* Retrieve the strinfo data for the string S that LEN was computed - from as some function F of strlen (S) (i.e., LEN need not be equal - to strlen(S)). */ - strinfo *silen = get_strinfo (pss->first); - - location_t callloc = gimple_or_expr_nonartificial_location (stmt, dst); - - tree func = gimple_call_fndecl (stmt); - - bool warned = false; - - /* When -Wstringop-truncation is set, try to determine truncation - before diagnosing possible overflow. Truncation is implied by - the LEN argument being equal to strlen(SRC), regardless of - whether its value is known. Otherwise, when appending, or - when copying into a destination of known size, issue the more - generic -Wstringop-overflow which triggers for LEN arguments - that in any meaningful way depend on strlen(SRC). */ - if (!append_p - && sisrc == silen - && is_strlen_related_p (src, len) - && warning_at (callloc, OPT_Wstringop_truncation, - "%qD output truncated before terminating nul " - "copying as many bytes from a string as its length", - func)) - warned = true; - else if ((append_p || !dstsize || len == dstlenp1) - && silen && is_strlen_related_p (src, silen->ptr)) - { - /* Issue -Wstringop-overflow when appending or when writing into - a destination of a known size. Otherwise, when copying into - a destination of an unknown size, it's truncation. */ - opt_code opt = (append_p || dstsize - ? OPT_Wstringop_overflow_ : OPT_Wstringop_truncation); - warned = warning_at (callloc, opt, - "%qD specified bound depends on the length " - "of the source argument", - func); - } - if (warned) - { - location_t strlenloc = pss->second; - if (strlenloc != UNKNOWN_LOCATION && strlenloc != callloc) - inform (strlenloc, "length computed here"); - } -} - -/* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call. - If strlen of the second argument is known and length of the third argument - is that plus one, strlen of the first argument is the same after this - call. Uses RVALS to determine range information. */ - -void -strlen_pass::handle_builtin_memcpy (built_in_function bcode) -{ - tree lhs, oldlen, newlen; - gimple *stmt = gsi_stmt (m_gsi); - strinfo *si, *dsi; - - tree len = gimple_call_arg (stmt, 2); - tree src = gimple_call_arg (stmt, 1); - tree dst = gimple_call_arg (stmt, 0); - - int didx = get_stridx (dst); - strinfo *olddsi = NULL; - if (didx > 0) - olddsi = get_strinfo (didx); - else if (didx < 0) - return; - - if (olddsi != NULL - && !integer_zerop (len)) - { - maybe_warn_overflow (stmt, false, len, olddsi, false, true); - adjust_last_stmt (olddsi, stmt, false); - } - - int idx = get_stridx (src); - if (idx == 0) - return; - - bool full_string_p; - if (idx > 0) - { - gimple *def_stmt; - - /* Handle memcpy (x, y, l) where l's relationship with strlen (y) - is known. */ - si = get_strinfo (idx); - if (si == NULL || si->nonzero_chars == NULL_TREE) - return; - if (TREE_CODE (len) == INTEGER_CST - && TREE_CODE (si->nonzero_chars) == INTEGER_CST) - { - if (tree_int_cst_le (len, si->nonzero_chars)) - { - /* Copying LEN nonzero characters, where LEN is constant. */ - newlen = len; - full_string_p = false; - } - else - { - /* Copying the whole of the analyzed part of SI. */ - newlen = si->nonzero_chars; - full_string_p = si->full_string_p; - } - } - else - { - if (!si->full_string_p) - return; - if (TREE_CODE (len) != SSA_NAME) - return; - def_stmt = SSA_NAME_DEF_STMT (len); - if (!is_gimple_assign (def_stmt) - || gimple_assign_rhs_code (def_stmt) != PLUS_EXPR - || gimple_assign_rhs1 (def_stmt) != si->nonzero_chars - || !integer_onep (gimple_assign_rhs2 (def_stmt))) - return; - /* Copying variable-length string SI (and no more). */ - newlen = si->nonzero_chars; - full_string_p = true; - } - } - else - { - si = NULL; - /* Handle memcpy (x, "abcd", 5) or - memcpy (x, "abc\0uvw", 7). */ - if (!tree_fits_uhwi_p (len)) - return; - - unsigned HOST_WIDE_INT clen = tree_to_uhwi (len); - unsigned HOST_WIDE_INT nonzero_chars = ~idx; - newlen = build_int_cst (size_type_node, MIN (nonzero_chars, clen)); - full_string_p = clen > nonzero_chars; - } - - if (!full_string_p - && olddsi - && olddsi->nonzero_chars - && TREE_CODE (olddsi->nonzero_chars) == INTEGER_CST - && tree_int_cst_le (newlen, olddsi->nonzero_chars)) - { - /* The SRC substring being written strictly overlaps - a subsequence of the existing string OLDDSI. */ - newlen = olddsi->nonzero_chars; - full_string_p = olddsi->full_string_p; - } - - if (olddsi != NULL && TREE_CODE (len) == SSA_NAME) - adjust_last_stmt (olddsi, stmt, false); - - if (didx == 0) - { - didx = new_stridx (dst); - if (didx == 0) - return; - } - oldlen = NULL_TREE; - if (olddsi != NULL) - { - dsi = unshare_strinfo (olddsi); - oldlen = olddsi->nonzero_chars; - dsi->nonzero_chars = newlen; - dsi->full_string_p = full_string_p; - /* Break the chain, so adjust_related_strinfo on later pointers in - the chain won't adjust this one anymore. */ - dsi->next = 0; - dsi->stmt = NULL; - dsi->endptr = NULL_TREE; - } - else - { - dsi = new_strinfo (dst, didx, newlen, full_string_p); - set_strinfo (didx, dsi); - find_equal_ptrs (dst, didx); - } - dsi->writable = true; - dsi->dont_invalidate = true; - if (olddsi != NULL) - { - tree adj = NULL_TREE; - location_t loc = gimple_location (stmt); - if (oldlen == NULL_TREE) - ; - else if (integer_zerop (oldlen)) - adj = newlen; - else if (TREE_CODE (oldlen) == INTEGER_CST - || TREE_CODE (newlen) == INTEGER_CST) - adj = fold_build2_loc (loc, MINUS_EXPR, TREE_TYPE (newlen), newlen, - fold_convert_loc (loc, TREE_TYPE (newlen), - oldlen)); - if (adj != NULL_TREE) - adjust_related_strinfos (loc, dsi, adj); - else - dsi->prev = 0; - } - /* memcpy src may not overlap dst, so src doesn't need to be - invalidated either. */ - if (si != NULL) - si->dont_invalidate = true; - - if (full_string_p) - { - lhs = gimple_call_lhs (stmt); - switch (bcode) - { - case BUILT_IN_MEMCPY: - case BUILT_IN_MEMCPY_CHK: - /* Allow adjust_last_stmt to decrease this memcpy's size. */ - laststmt.stmt = stmt; - laststmt.len = dsi->nonzero_chars; - laststmt.stridx = dsi->idx; - if (lhs) - ssa_ver_to_stridx[SSA_NAME_VERSION (lhs)] = didx; - break; - case BUILT_IN_MEMPCPY: - case BUILT_IN_MEMPCPY_CHK: - break; - default: - gcc_unreachable (); - } - } -} - -/* Handle a strcat-like ({strcat,__strcat_chk}) call. - If strlen of the second argument is known, strlen of the first argument - is increased by the length of the second argument. Furthermore, attempt - to convert it to memcpy/strcpy if the length of the first argument - is known. */ - -void -strlen_pass::handle_builtin_strcat (built_in_function bcode) -{ - int idx, didx; - tree srclen, args, type, fn, objsz, endptr; - bool success; - gimple *stmt = gsi_stmt (m_gsi); - strinfo *si, *dsi; - location_t loc = gimple_location (stmt); - - tree src = gimple_call_arg (stmt, 1); - tree dst = gimple_call_arg (stmt, 0); - - /* Bail if the source is the same as destination. It will be diagnosed - elsewhere. */ - if (operand_equal_p (src, dst, 0)) - return; - - tree lhs = gimple_call_lhs (stmt); - - didx = get_stridx (dst); - if (didx < 0) - return; - - dsi = NULL; - if (didx > 0) - dsi = get_strinfo (didx); - - srclen = NULL_TREE; - si = NULL; - idx = get_stridx (src); - if (idx < 0) - srclen = build_int_cst (size_type_node, ~idx); - else if (idx > 0) - { - si = get_strinfo (idx); - if (si != NULL) - srclen = get_string_length (si); - } - - /* Disable warning for the transformed statement? */ - opt_code no_warning_opt = no_warning; - - if (dsi == NULL || get_string_length (dsi) == NULL_TREE) - { - { - /* The concatenation always involves copying at least one byte - (the terminating nul), even if the source string is empty. - If the source is unknown assume it's one character long and - used that as both sizes. */ - tree slen = srclen; - if (slen) - { - tree type = TREE_TYPE (slen); - slen = fold_build2 (PLUS_EXPR, type, slen, build_int_cst (type, 1)); - } - - tree sptr = si && si->ptr ? si->ptr : src; - no_warning_opt = check_bounds_or_overlap (stmt, dst, sptr, NULL_TREE, - slen); - if (no_warning_opt) - suppress_warning (stmt, no_warning_opt); - } - - /* strcat (p, q) can be transformed into - tmp = p + strlen (p); endptr = stpcpy (tmp, q); - with length endptr - p if we need to compute the length - later on. Don't do this transformation if we don't need - it. */ - if (builtin_decl_implicit_p (BUILT_IN_STPCPY) && lhs == NULL_TREE) - { - if (didx == 0) - { - didx = new_stridx (dst); - if (didx == 0) - return; - } - if (dsi == NULL) - { - dsi = new_strinfo (dst, didx, NULL_TREE, false); - set_strinfo (didx, dsi); - find_equal_ptrs (dst, didx); - } - else - { - dsi = unshare_strinfo (dsi); - dsi->nonzero_chars = NULL_TREE; - dsi->full_string_p = false; - dsi->next = 0; - dsi->endptr = NULL_TREE; - } - dsi->writable = true; - dsi->stmt = stmt; - dsi->dont_invalidate = true; - } - return; - } - - tree dstlen = dsi->nonzero_chars; - endptr = dsi->endptr; - - dsi = unshare_strinfo (dsi); - dsi->endptr = NULL_TREE; - dsi->stmt = NULL; - dsi->writable = true; - - if (srclen != NULL_TREE) - { - dsi->nonzero_chars = fold_build2_loc (loc, PLUS_EXPR, - TREE_TYPE (dsi->nonzero_chars), - dsi->nonzero_chars, srclen); - gcc_assert (dsi->full_string_p); - adjust_related_strinfos (loc, dsi, srclen); - dsi->dont_invalidate = true; - } - else - { - dsi->nonzero_chars = NULL; - dsi->full_string_p = false; - if (lhs == NULL_TREE && builtin_decl_implicit_p (BUILT_IN_STPCPY)) - dsi->dont_invalidate = true; - } - - if (si != NULL) - /* strcat src may not overlap dst, so src doesn't need to be - invalidated either. */ - si->dont_invalidate = true; - - /* For now. Could remove the lhs from the call and add - lhs = dst; afterwards. */ - if (lhs) - return; - - fn = NULL_TREE; - objsz = NULL_TREE; - switch (bcode) - { - case BUILT_IN_STRCAT: - if (srclen != NULL_TREE) - fn = builtin_decl_implicit (BUILT_IN_MEMCPY); - else - fn = builtin_decl_implicit (BUILT_IN_STRCPY); - break; - case BUILT_IN_STRCAT_CHK: - if (srclen != NULL_TREE) - fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK); - else - fn = builtin_decl_explicit (BUILT_IN_STRCPY_CHK); - objsz = gimple_call_arg (stmt, 2); - break; - default: - gcc_unreachable (); - } - - if (fn == NULL_TREE) - return; - - if (dsi && dstlen) - { - tree type = TREE_TYPE (dstlen); - - /* Compute the size of the source sequence, including the nul. */ - tree srcsize = srclen ? srclen : size_zero_node; - tree one = build_int_cst (type, 1); - srcsize = fold_build2 (PLUS_EXPR, type, srcsize, one); - tree dstsize = fold_build2 (PLUS_EXPR, type, dstlen, one); - tree sptr = si && si->ptr ? si->ptr : src; - - no_warning_opt = check_bounds_or_overlap (stmt, dst, sptr, dstsize, - srcsize); - if (no_warning_opt) - suppress_warning (stmt, no_warning_opt); - } - - tree len = NULL_TREE; - if (srclen != NULL_TREE) - { - args = TYPE_ARG_TYPES (TREE_TYPE (fn)); - type = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args))); - - len = fold_convert_loc (loc, type, unshare_expr (srclen)); - len = fold_build2_loc (loc, PLUS_EXPR, type, len, - build_int_cst (type, 1)); - len = force_gimple_operand_gsi (&m_gsi, len, true, NULL_TREE, true, - GSI_SAME_STMT); - } - if (endptr) - dst = fold_convert_loc (loc, TREE_TYPE (dst), unshare_expr (endptr)); - else - dst = fold_build2_loc (loc, POINTER_PLUS_EXPR, TREE_TYPE (dst), dst, - fold_convert_loc (loc, sizetype, - unshare_expr (dstlen))); - dst = force_gimple_operand_gsi (&m_gsi, dst, true, NULL_TREE, true, - GSI_SAME_STMT); - if (objsz) - { - objsz = fold_build2_loc (loc, MINUS_EXPR, TREE_TYPE (objsz), objsz, - fold_convert_loc (loc, TREE_TYPE (objsz), - unshare_expr (dstlen))); - objsz = force_gimple_operand_gsi (&m_gsi, objsz, true, NULL_TREE, true, - GSI_SAME_STMT); - } - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "Optimizing: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - if (srclen != NULL_TREE) - success = update_gimple_call (&m_gsi, fn, 3 + (objsz != NULL_TREE), - dst, src, len, objsz); - else - success = update_gimple_call (&m_gsi, fn, 2 + (objsz != NULL_TREE), - dst, src, objsz); - if (success) - { - stmt = gsi_stmt (m_gsi); - update_stmt (stmt); - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "into: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - /* If srclen == NULL, note that current string length can be - computed by transforming this strcpy into stpcpy. */ - if (srclen == NULL_TREE && dsi->dont_invalidate) - dsi->stmt = stmt; - adjust_last_stmt (dsi, stmt, true); - if (srclen != NULL_TREE) - { - laststmt.stmt = stmt; - laststmt.len = srclen; - laststmt.stridx = dsi->idx; - } - } - else if (dump_file && (dump_flags & TDF_DETAILS) != 0) - fprintf (dump_file, "not possible.\n"); - - if (no_warning_opt) - suppress_warning (stmt, no_warning_opt); -} - -/* Handle a call to an allocation function like alloca, malloc or calloc, - or an ordinary allocation function declared with attribute alloc_size. */ - -void -strlen_pass::handle_alloc_call (built_in_function bcode) -{ - gimple *stmt = gsi_stmt (m_gsi); - tree lhs = gimple_call_lhs (stmt); - if (lhs == NULL_TREE) - return; - - gcc_assert (get_stridx (lhs) == 0); - int idx = new_stridx (lhs); - tree length = NULL_TREE; - if (bcode == BUILT_IN_CALLOC) - length = build_int_cst (size_type_node, 0); - strinfo *si = new_strinfo (lhs, idx, length, length != NULL_TREE); - if (bcode == BUILT_IN_CALLOC) - { - /* Only set STMT for calloc and malloc. */ - si->stmt = stmt; - /* Only set ENDPTR for calloc. */ - si->endptr = lhs; - } - else if (bcode == BUILT_IN_MALLOC) - si->stmt = stmt; - - /* Set ALLOC is set for all allocation functions. */ - si->alloc = stmt; - set_strinfo (idx, si); - si->writable = true; - si->dont_invalidate = true; -} - -/* Handle a call to memset. - After a call to calloc, memset(,0,) is unnecessary. - memset(malloc(n),0,n) is calloc(n,1). - return true when the call is transformed, false otherwise. - When nonnull uses RVALS to determine range information. */ - -bool -strlen_pass::handle_builtin_memset (bool *zero_write) -{ - gimple *memset_stmt = gsi_stmt (m_gsi); - tree ptr = gimple_call_arg (memset_stmt, 0); - /* Set to the non-constant offset added to PTR. */ - wide_int offrng[2]; - int idx1 = get_stridx (ptr, offrng, ptr_qry.rvals); - if (idx1 <= 0) - return false; - strinfo *si1 = get_strinfo (idx1); - if (!si1) - return false; - gimple *alloc_stmt = si1->alloc; - if (!alloc_stmt || !is_gimple_call (alloc_stmt)) - return false; - tree callee1 = gimple_call_fndecl (alloc_stmt); - if (!valid_builtin_call (alloc_stmt)) - return false; - tree alloc_size = gimple_call_arg (alloc_stmt, 0); - tree memset_size = gimple_call_arg (memset_stmt, 2); - - /* Check for overflow. */ - maybe_warn_overflow (memset_stmt, false, memset_size, NULL, false, true); - - /* Bail when there is no statement associated with the destination - (the statement may be null even when SI1->ALLOC is not). */ - if (!si1->stmt) - return false; - - /* Avoid optimizing if store is at a variable offset from the beginning - of the allocated object. */ - if (offrng[0] != 0 || offrng[0] != offrng[1]) - return false; - - /* Bail when the call writes a non-zero value. */ - if (!integer_zerop (gimple_call_arg (memset_stmt, 1))) - return false; - - /* Let the caller know the memset call cleared the destination. */ - *zero_write = true; - - enum built_in_function code1 = DECL_FUNCTION_CODE (callee1); - if (code1 == BUILT_IN_CALLOC) - /* Not touching alloc_stmt */ ; - else if (code1 == BUILT_IN_MALLOC - && operand_equal_p (memset_size, alloc_size, 0)) - { - /* Replace the malloc + memset calls with calloc. */ - gimple_stmt_iterator gsi1 = gsi_for_stmt (si1->stmt); - update_gimple_call (&gsi1, builtin_decl_implicit (BUILT_IN_CALLOC), 2, - alloc_size, build_one_cst (size_type_node)); - si1->nonzero_chars = build_int_cst (size_type_node, 0); - si1->full_string_p = true; - si1->stmt = gsi_stmt (gsi1); - } - else - return false; - tree lhs = gimple_call_lhs (memset_stmt); - unlink_stmt_vdef (memset_stmt); - if (lhs) - { - gimple *assign = gimple_build_assign (lhs, ptr); - gsi_replace (&m_gsi, assign, false); - } - else - { - gsi_remove (&m_gsi, true); - release_defs (memset_stmt); - } - - return true; -} - -/* Return first such statement if RES is used in statements testing its - equality to zero, and null otherwise. If EXCLUSIVE is true, return - nonnull if and only RES is used in such expressions exclusively and - in none other. */ - -static gimple * -use_in_zero_equality (tree res, bool exclusive = true) -{ - gimple *first_use = NULL; - - use_operand_p use_p; - imm_use_iterator iter; - - FOR_EACH_IMM_USE_FAST (use_p, iter, res) - { - gimple *use_stmt = USE_STMT (use_p); - - if (is_gimple_debug (use_stmt)) - continue; - - if (gimple_code (use_stmt) == GIMPLE_ASSIGN) - { - tree_code code = gimple_assign_rhs_code (use_stmt); - if (code == COND_EXPR) - { - tree cond_expr = gimple_assign_rhs1 (use_stmt); - if ((TREE_CODE (cond_expr) != EQ_EXPR - && (TREE_CODE (cond_expr) != NE_EXPR)) - || !integer_zerop (TREE_OPERAND (cond_expr, 1))) - { - if (exclusive) - return NULL; - continue; - } - } - else if (code == EQ_EXPR || code == NE_EXPR) - { - if (!integer_zerop (gimple_assign_rhs2 (use_stmt))) - { - if (exclusive) - return NULL; - continue; - } - } - else if (exclusive) - return NULL; - else - continue; - } - else if (gimple_code (use_stmt) == GIMPLE_COND) - { - tree_code code = gimple_cond_code (use_stmt); - if ((code != EQ_EXPR && code != NE_EXPR) - || !integer_zerop (gimple_cond_rhs (use_stmt))) - { - if (exclusive) - return NULL; - continue; - } - } - else if (exclusive) - return NULL; - else - continue; - - if (!first_use) - first_use = use_stmt; - } - - return first_use; -} - -/* Handle a call to memcmp. We try to handle small comparisons by - converting them to load and compare, and replacing the call to memcmp - with a __builtin_memcmp_eq call where possible. - return true when call is transformed, return false otherwise. */ - -bool -strlen_pass::handle_builtin_memcmp () -{ - gcall *stmt = as_a <gcall *> (gsi_stmt (m_gsi)); - tree res = gimple_call_lhs (stmt); - - if (!res || !use_in_zero_equality (res)) - return false; - - tree arg1 = gimple_call_arg (stmt, 0); - tree arg2 = gimple_call_arg (stmt, 1); - tree len = gimple_call_arg (stmt, 2); - unsigned HOST_WIDE_INT leni; - - if (tree_fits_uhwi_p (len) - && (leni = tree_to_uhwi (len)) <= GET_MODE_SIZE (word_mode) - && pow2p_hwi (leni)) - { - leni *= CHAR_TYPE_SIZE; - unsigned align1 = get_pointer_alignment (arg1); - unsigned align2 = get_pointer_alignment (arg2); - unsigned align = MIN (align1, align2); - scalar_int_mode mode; - if (int_mode_for_size (leni, 1).exists (&mode) - && (align >= leni || !targetm.slow_unaligned_access (mode, align))) - { - location_t loc = gimple_location (stmt); - tree type, off; - type = build_nonstandard_integer_type (leni, 1); - gcc_assert (known_eq (GET_MODE_BITSIZE (TYPE_MODE (type)), leni)); - tree ptrtype = build_pointer_type_for_mode (char_type_node, - ptr_mode, true); - off = build_int_cst (ptrtype, 0); - arg1 = build2_loc (loc, MEM_REF, type, arg1, off); - arg2 = build2_loc (loc, MEM_REF, type, arg2, off); - tree tem1 = fold_const_aggregate_ref (arg1); - if (tem1) - arg1 = tem1; - tree tem2 = fold_const_aggregate_ref (arg2); - if (tem2) - arg2 = tem2; - res = fold_convert_loc (loc, TREE_TYPE (res), - fold_build2_loc (loc, NE_EXPR, - boolean_type_node, - arg1, arg2)); - gimplify_and_update_call_from_tree (&m_gsi, res); - return true; - } - } - - gimple_call_set_fndecl (stmt, builtin_decl_explicit (BUILT_IN_MEMCMP_EQ)); - return true; -} - -/* Given strinfo IDX for ARG, sets LENRNG[] to the range of lengths - of the string(s) referenced by ARG if it can be determined. - If the length cannot be determined, sets *SIZE to the size of - the array the string is stored in, if any. If no such array is - known, sets *SIZE to -1. When the strings are nul-terminated sets - *NULTERM to true, otherwise to false. When nonnull uses RVALS to - determine range information. Returns true on success. */ - -bool -strlen_pass::get_len_or_size (gimple *stmt, tree arg, int idx, - unsigned HOST_WIDE_INT lenrng[2], - unsigned HOST_WIDE_INT *size, bool *nulterm) -{ - /* Invalidate. */ - *size = HOST_WIDE_INT_M1U; - - if (idx < 0) - { - /* IDX is the inverted constant string length. */ - lenrng[0] = ~idx; - lenrng[1] = lenrng[0]; - *nulterm = true; - return true; - } - - /* Set so that both LEN and ~LEN are invalid lengths, i.e., maximum - possible length + 1. */ - lenrng[0] = lenrng[1] = HOST_WIDE_INT_MAX; - - if (strinfo *si = idx ? get_strinfo (idx) : NULL) - { - /* FIXME: Handle all this in_range_strlen_dynamic. */ - if (!si->nonzero_chars) - ; - else if (tree_fits_uhwi_p (si->nonzero_chars)) - { - lenrng[0] = tree_to_uhwi (si->nonzero_chars); - *nulterm = si->full_string_p; - /* Set the upper bound only if the string is known to be - nul-terminated, otherwise leave it at maximum + 1. */ - if (*nulterm) - lenrng[1] = lenrng[0]; - } - else if (TREE_CODE (si->nonzero_chars) == SSA_NAME) - { - value_range r; - get_range_query (cfun)->range_of_expr (r, si->nonzero_chars); - if (r.kind () == VR_RANGE) - { - lenrng[0] = r.lower_bound ().to_uhwi (); - lenrng[1] = r.upper_bound ().to_uhwi (); - *nulterm = si->full_string_p; - } - } - } - - if (lenrng[0] != HOST_WIDE_INT_MAX) - return true; - - /* Compute the minimum and maximum real or possible lengths. */ - c_strlen_data lendata = { }; - /* Set MAXBOUND to an arbitrary non-null non-integer node as a request - to have it set to the length of the longest string in a PHI. */ - lendata.maxbound = arg; - get_range_strlen_dynamic (arg, stmt, &lendata, ptr_qry.rvals); - - unsigned HOST_WIDE_INT maxbound = HOST_WIDE_INT_M1U; - if (tree_fits_uhwi_p (lendata.maxbound) - && !integer_all_onesp (lendata.maxbound)) - maxbound = tree_to_uhwi (lendata.maxbound); - - if (tree_fits_uhwi_p (lendata.minlen) && tree_fits_uhwi_p (lendata.maxlen)) - { - unsigned HOST_WIDE_INT minlen = tree_to_uhwi (lendata.minlen); - unsigned HOST_WIDE_INT maxlen = tree_to_uhwi (lendata.maxlen); - - /* The longest string in this data model. */ - const unsigned HOST_WIDE_INT lenmax - = tree_to_uhwi (max_object_size ()) - 2; - - if (maxbound == HOST_WIDE_INT_M1U) - { - lenrng[0] = minlen; - lenrng[1] = maxlen; - *nulterm = minlen == maxlen; - } - else if (maxlen < lenmax) - { - *size = maxbound + 1; - *nulterm = false; - } - else - return false; - - return true; - } - - if (maxbound != HOST_WIDE_INT_M1U - && lendata.maxlen - && !integer_all_onesp (lendata.maxlen)) - { - /* Set *SIZE to LENDATA.MAXBOUND which is a conservative estimate - of the longest string based on the sizes of the arrays referenced - by ARG. */ - *size = maxbound + 1; - *nulterm = false; - return true; - } - - return false; -} - -/* If IDX1 and IDX2 refer to strings A and B of unequal lengths, return - the result of 0 == strncmp (A, B, BOUND) (which is the same as strcmp - for a sufficiently large BOUND). If the result is based on the length - of one string being greater than the longest string that would fit in - the array pointer to by the argument, set *PLEN and *PSIZE to - the corresponding length (or its complement when the string is known - to be at least as long and need not be nul-terminated) and size. - Otherwise return null. */ - -tree -strlen_pass::strxcmp_eqz_result (gimple *stmt, tree arg1, int idx1, - tree arg2, int idx2, - unsigned HOST_WIDE_INT bound, - unsigned HOST_WIDE_INT len[2], - unsigned HOST_WIDE_INT *psize) -{ - /* Determine the range the length of each string is in and whether it's - known to be nul-terminated, or the size of the array it's stored in. */ - bool nul1, nul2; - unsigned HOST_WIDE_INT siz1, siz2; - unsigned HOST_WIDE_INT len1rng[2], len2rng[2]; - if (!get_len_or_size (stmt, arg1, idx1, len1rng, &siz1, &nul1) - || !get_len_or_size (stmt, arg2, idx2, len2rng, &siz2, &nul2)) - return NULL_TREE; - - /* BOUND is set to HWI_M1U for strcmp and less to strncmp, and LENiRNG - to HWI_MAX when invalid. Adjust the length of each string to consider - to be no more than BOUND. */ - if (len1rng[0] < HOST_WIDE_INT_MAX && len1rng[0] > bound) - len1rng[0] = bound; - if (len1rng[1] < HOST_WIDE_INT_MAX && len1rng[1] > bound) - len1rng[1] = bound; - if (len2rng[0] < HOST_WIDE_INT_MAX && len2rng[0] > bound) - len2rng[0] = bound; - if (len2rng[1] < HOST_WIDE_INT_MAX && len2rng[1] > bound) - len2rng[1] = bound; - - /* Two empty strings are equal. */ - if (len1rng[1] == 0 && len2rng[1] == 0) - return integer_one_node; - - /* The strings are definitely unequal when the lower bound of the length - of one of them is greater than the length of the longest string that - would fit into the other array. */ - if (len1rng[0] == HOST_WIDE_INT_MAX - && len2rng[0] != HOST_WIDE_INT_MAX - && ((len2rng[0] < bound && len2rng[0] >= siz1) - || len2rng[0] > siz1)) - { - *psize = siz1; - len[0] = len1rng[0]; - /* Set LEN[0] to the lower bound of ARG1's length when it's - nul-terminated or to the complement of its minimum length - otherwise, */ - len[1] = nul2 ? len2rng[0] : ~len2rng[0]; - return integer_zero_node; - } - - if (len2rng[0] == HOST_WIDE_INT_MAX - && len1rng[0] != HOST_WIDE_INT_MAX - && ((len1rng[0] < bound && len1rng[0] >= siz2) - || len1rng[0] > siz2)) - { - *psize = siz2; - len[0] = nul1 ? len1rng[0] : ~len1rng[0]; - len[1] = len2rng[0]; - return integer_zero_node; - } - - /* The strings are also definitely unequal when their lengths are unequal - and at least one is nul-terminated. */ - if (len1rng[0] != HOST_WIDE_INT_MAX - && len2rng[0] != HOST_WIDE_INT_MAX - && ((len1rng[1] < len2rng[0] && nul1) - || (len2rng[1] < len1rng[0] && nul2))) - { - if (bound <= len1rng[0] || bound <= len2rng[0]) - *psize = bound; - else - *psize = HOST_WIDE_INT_M1U; - - len[0] = len1rng[0]; - len[1] = len2rng[0]; - return integer_zero_node; - } - - /* The string lengths may be equal or unequal. Even when equal and - both strings nul-terminated, without the string contents there's - no way to determine whether they are equal. */ - return NULL_TREE; -} - -/* Diagnose pointless calls to strcmp or strncmp STMT with string - arguments of lengths LEN or size SIZ and (for strncmp) BOUND, - whose result is used in equality expressions that evaluate to - a constant due to one argument being longer than the size of - the other. */ - -static void -maybe_warn_pointless_strcmp (gimple *stmt, HOST_WIDE_INT bound, - unsigned HOST_WIDE_INT len[2], - unsigned HOST_WIDE_INT siz) -{ - tree lhs = gimple_call_lhs (stmt); - gimple *use = use_in_zero_equality (lhs, /* exclusive = */ false); - if (!use) - return; - - bool at_least = false; - - /* Excessive LEN[i] indicates a lower bound. */ - if (len[0] > HOST_WIDE_INT_MAX) - { - at_least = true; - len[0] = ~len[0]; - } - - if (len[1] > HOST_WIDE_INT_MAX) - { - at_least = true; - len[1] = ~len[1]; - } - - unsigned HOST_WIDE_INT minlen = MIN (len[0], len[1]); - - /* FIXME: Include a note pointing to the declaration of the smaller - array. */ - location_t stmt_loc = gimple_or_expr_nonartificial_location (stmt, lhs); - - tree callee = gimple_call_fndecl (stmt); - bool warned = false; - if (siz <= minlen && bound == -1) - warned = warning_at (stmt_loc, OPT_Wstring_compare, - (at_least - ? G_("%qD of a string of length %wu or more and " - "an array of size %wu evaluates to nonzero") - : G_("%qD of a string of length %wu and an array " - "of size %wu evaluates to nonzero")), - callee, minlen, siz); - else if (!at_least && siz <= HOST_WIDE_INT_MAX) - { - if (len[0] != HOST_WIDE_INT_MAX && len[1] != HOST_WIDE_INT_MAX) - warned = warning_at (stmt_loc, OPT_Wstring_compare, - "%qD of strings of length %wu and %wu " - "and bound of %wu evaluates to nonzero", - callee, len[0], len[1], bound); - else - warned = warning_at (stmt_loc, OPT_Wstring_compare, - "%qD of a string of length %wu, an array " - "of size %wu and bound of %wu evaluates to " - "nonzero", - callee, minlen, siz, bound); - } - - if (!warned) - return; - - location_t use_loc = gimple_location (use); - if (LOCATION_LINE (stmt_loc) != LOCATION_LINE (use_loc)) - inform (use_loc, "in this expression"); -} - - -/* Optimize a call to strcmp or strncmp either by folding it to a constant - when possible or by transforming the latter to the former. Warn about - calls where the length of one argument is greater than the size of - the array to which the other argument points if the latter's length - is not known. Return true when the call has been transformed into - another and false otherwise. */ - -bool -strlen_pass::handle_builtin_string_cmp () -{ - gcall *stmt = as_a <gcall *> (gsi_stmt (m_gsi)); - tree lhs = gimple_call_lhs (stmt); - - if (!lhs) - return false; - - tree arg1 = gimple_call_arg (stmt, 0); - tree arg2 = gimple_call_arg (stmt, 1); - int idx1 = get_stridx (arg1); - int idx2 = get_stridx (arg2); - - /* For strncmp set to the value of the third argument if known. */ - HOST_WIDE_INT bound = -1; - tree len = NULL_TREE; - /* Extract the strncmp bound. */ - if (gimple_call_num_args (stmt) == 3) - { - len = gimple_call_arg (stmt, 2); - if (tree_fits_shwi_p (len)) - bound = tree_to_shwi (len); - - /* If the bound argument is NOT known, do nothing. */ - if (bound < 0) - return false; - } - - /* Avoid folding if either argument is not a nul-terminated array. - Defer warning until later. */ - if (!check_nul_terminated_array (NULL_TREE, arg1, len) - || !check_nul_terminated_array (NULL_TREE, arg2, len)) - return false; - - { - /* Set to the length of one argument (or its complement if it's - the lower bound of a range) and the size of the array storing - the other if the result is based on the former being equal to - or greater than the latter. */ - unsigned HOST_WIDE_INT len[2] = { HOST_WIDE_INT_MAX, HOST_WIDE_INT_MAX }; - unsigned HOST_WIDE_INT siz = HOST_WIDE_INT_M1U; - - /* Try to determine if the two strings are either definitely equal - or definitely unequal and if so, either fold the result to zero - (when equal) or set the range of the result to ~[0, 0] otherwise. */ - if (tree eqz = strxcmp_eqz_result (stmt, arg1, idx1, arg2, idx2, bound, - len, &siz)) - { - if (integer_zerop (eqz)) - { - maybe_warn_pointless_strcmp (stmt, bound, len, siz); - - /* When the lengths of the first two string arguments are - known to be unequal set the range of the result to non-zero. - This allows the call to be eliminated if its result is only - used in tests for equality to zero. */ - wide_int zero = wi::zero (TYPE_PRECISION (TREE_TYPE (lhs))); - set_range_info (lhs, VR_ANTI_RANGE, zero, zero); - return false; - } - /* When the two strings are definitely equal (such as when they - are both empty) fold the call to the constant result. */ - replace_call_with_value (&m_gsi, integer_zero_node); - return true; - } - } - - /* Return if nothing is known about the strings pointed to by ARG1 - and ARG2. */ - if (idx1 == 0 && idx2 == 0) - return false; - - /* Determine either the length or the size of each of the strings, - whichever is available. */ - HOST_WIDE_INT cstlen1 = -1, cstlen2 = -1; - HOST_WIDE_INT arysiz1 = -1, arysiz2 = -1; - - { - unsigned HOST_WIDE_INT len1rng[2], len2rng[2]; - unsigned HOST_WIDE_INT arsz1, arsz2; - bool nulterm[2]; - - if (!get_len_or_size (stmt, arg1, idx1, len1rng, &arsz1, nulterm) - || !get_len_or_size (stmt, arg2, idx2, len2rng, &arsz2, nulterm + 1)) - return false; - - if (len1rng[0] == len1rng[1] && len1rng[0] < HOST_WIDE_INT_MAX) - cstlen1 = len1rng[0]; - else if (arsz1 < HOST_WIDE_INT_M1U) - arysiz1 = arsz1; - - if (len2rng[0] == len2rng[1] && len2rng[0] < HOST_WIDE_INT_MAX) - cstlen2 = len2rng[0]; - else if (arsz2 < HOST_WIDE_INT_M1U) - arysiz2 = arsz2; - } - - /* Bail if neither the string length nor the size of the array - it is stored in can be determined. */ - if ((cstlen1 < 0 && arysiz1 < 0) - || (cstlen2 < 0 && arysiz2 < 0) - || (cstlen1 < 0 && cstlen2 < 0)) - return false; - - if (cstlen1 >= 0) - ++cstlen1; - if (cstlen2 >= 0) - ++cstlen2; - - /* The exact number of characters to compare. */ - HOST_WIDE_INT cmpsiz; - if (cstlen1 >= 0 && cstlen2 >= 0) - cmpsiz = MIN (cstlen1, cstlen2); - else if (cstlen1 >= 0) - cmpsiz = cstlen1; - else - cmpsiz = cstlen2; - if (bound >= 0) - cmpsiz = MIN (cmpsiz, bound); - /* The size of the array in which the unknown string is stored. */ - HOST_WIDE_INT varsiz = arysiz1 < 0 ? arysiz2 : arysiz1; - - if ((varsiz < 0 || cmpsiz < varsiz) && use_in_zero_equality (lhs)) - { - /* If the known length is less than the size of the other array - and the strcmp result is only used to test equality to zero, - transform the call to the equivalent _eq call. */ - if (tree fn = builtin_decl_implicit (bound < 0 ? BUILT_IN_STRCMP_EQ - : BUILT_IN_STRNCMP_EQ)) - { - tree n = build_int_cst (size_type_node, cmpsiz); - update_gimple_call (&m_gsi, fn, 3, arg1, arg2, n); - return true; - } - } - - return false; -} - -/* Handle a POINTER_PLUS_EXPR statement. - For p = "abcd" + 2; compute associated length, or if - p = q + off is pointing to a '\0' character of a string, call - zero_length_string on it. */ - -void -strlen_pass::handle_pointer_plus () -{ - gimple *stmt = gsi_stmt (m_gsi); - tree lhs = gimple_assign_lhs (stmt), off; - int idx = get_stridx (gimple_assign_rhs1 (stmt)); - strinfo *si, *zsi; - - if (idx == 0) - return; - - if (idx < 0) - { - tree off = gimple_assign_rhs2 (stmt); - if (tree_fits_uhwi_p (off) - && tree_to_uhwi (off) <= (unsigned HOST_WIDE_INT) ~idx) - ssa_ver_to_stridx[SSA_NAME_VERSION (lhs)] - = ~(~idx - (int) tree_to_uhwi (off)); - return; - } - - si = get_strinfo (idx); - if (si == NULL || si->nonzero_chars == NULL_TREE) - return; - - off = gimple_assign_rhs2 (stmt); - zsi = NULL; - if (si->full_string_p && operand_equal_p (si->nonzero_chars, off, 0)) - zsi = zero_length_string (lhs, si); - else if (TREE_CODE (off) == SSA_NAME) - { - gimple *def_stmt = SSA_NAME_DEF_STMT (off); - if (gimple_assign_single_p (def_stmt) - && si->full_string_p - && operand_equal_p (si->nonzero_chars, - gimple_assign_rhs1 (def_stmt), 0)) - zsi = zero_length_string (lhs, si); - } - if (zsi != NULL - && si->endptr != NULL_TREE - && si->endptr != lhs - && TREE_CODE (si->endptr) == SSA_NAME) - { - enum tree_code rhs_code - = useless_type_conversion_p (TREE_TYPE (lhs), TREE_TYPE (si->endptr)) - ? SSA_NAME : NOP_EXPR; - gimple_assign_set_rhs_with_ops (&m_gsi, rhs_code, si->endptr); - gcc_assert (gsi_stmt (m_gsi) == stmt); - update_stmt (stmt); - } -} - -/* Set LENRANGE to the number of nonzero bytes for a store of TYPE and - clear all flags. Return true on success and false on failure. */ - -static bool -nonzero_bytes_for_type (tree type, unsigned lenrange[3], - bool *nulterm, bool *allnul, bool *allnonnul) -{ - /* Use the size of the type of the expression as the size of the store, - and set the upper bound of the length range to that of the size. - Nothing is known about the contents so clear all flags. */ - tree typesize = TYPE_SIZE_UNIT (type); - if (!type) - return false; - - if (!tree_fits_uhwi_p (typesize)) - return false; - - unsigned HOST_WIDE_INT sz = tree_to_uhwi (typesize); - if (sz > UINT_MAX) - return false; - - lenrange[2] = sz; - lenrange[1] = lenrange[2] ? lenrange[2] - 1 : 0; - lenrange[0] = 0; - *nulterm = false; - *allnul = false; - *allnonnul = false; - return true; -} - -/* Recursively determine the minimum and maximum number of leading nonzero - bytes in the representation of EXP and set LENRANGE[0] and LENRANGE[1] - to each. - Sets LENRANGE[2] to the total size of the access (which may be less - than LENRANGE[1] when what's being referenced by EXP is a pointer - rather than an array). - Sets *NULTERM if the representation contains a zero byte, sets *ALLNUL - if all the bytes are zero, and *ALLNONNUL is all are nonzero. - OFFSET and NBYTES are the offset into the representation and - the size of the access to it determined from an ADDR_EXPR (i.e., - a pointer) or MEM_REF or zero for other expressions. - Uses RVALS to determine range information. - Avoids recursing deeper than the limits in SNLIM allow. - Returns true on success and false otherwise. */ - -bool -strlen_pass::count_nonzero_bytes (tree exp, - unsigned HOST_WIDE_INT offset, - unsigned HOST_WIDE_INT nbytes, - unsigned lenrange[3], bool *nulterm, - bool *allnul, bool *allnonnul, - ssa_name_limit_t &snlim) -{ - if (TREE_CODE (exp) == SSA_NAME) - { - /* Handle non-zero single-character stores specially. */ - tree type = TREE_TYPE (exp); - if (TREE_CODE (type) == INTEGER_TYPE - && TYPE_MODE (type) == TYPE_MODE (char_type_node) - && TYPE_PRECISION (type) == TYPE_PRECISION (char_type_node) - && tree_expr_nonzero_p (exp)) - { - /* If the character EXP is known to be non-zero (even if its - exact value is not known) recurse once to set the range - for an arbitrary constant. */ - exp = build_int_cst (type, 1); - return count_nonzero_bytes (exp, offset, 1, lenrange, - nulterm, allnul, allnonnul, snlim); - } - - gimple *stmt = SSA_NAME_DEF_STMT (exp); - if (gimple_assign_single_p (stmt)) - { - exp = gimple_assign_rhs1 (stmt); - if (!DECL_P (exp) - && TREE_CODE (exp) != CONSTRUCTOR - && TREE_CODE (exp) != MEM_REF) - return false; - /* Handle DECLs, CONSTRUCTOR and MEM_REF below. */ - } - else if (gimple_code (stmt) == GIMPLE_PHI) - { - /* Avoid processing an SSA_NAME that has already been visited - or if an SSA_NAME limit has been reached. Indicate success - if the former and failure if the latter. */ - if (int res = snlim.next_phi (exp)) - return res > 0; - - /* Determine the minimum and maximum from the PHI arguments. */ - unsigned int n = gimple_phi_num_args (stmt); - for (unsigned i = 0; i != n; i++) - { - tree def = gimple_phi_arg_def (stmt, i); - if (!count_nonzero_bytes (def, offset, nbytes, lenrange, nulterm, - allnul, allnonnul, snlim)) - return false; - } - - return true; - } - } - - if (TREE_CODE (exp) == CONSTRUCTOR) - { - if (nbytes) - /* If NBYTES has already been determined by an outer MEM_REF - fail rather than overwriting it (this shouldn't happen). */ - return false; - - tree type = TREE_TYPE (exp); - tree size = TYPE_SIZE_UNIT (type); - if (!size || !tree_fits_uhwi_p (size)) - return false; - - unsigned HOST_WIDE_INT byte_size = tree_to_uhwi (size); - if (byte_size < offset) - return false; - - nbytes = byte_size - offset; - } - - if (TREE_CODE (exp) == MEM_REF) - { - if (nbytes) - return false; - - tree arg = TREE_OPERAND (exp, 0); - tree off = TREE_OPERAND (exp, 1); - - if (TREE_CODE (off) != INTEGER_CST || !tree_fits_uhwi_p (off)) - return false; - - unsigned HOST_WIDE_INT wioff = tree_to_uhwi (off); - if (INT_MAX < wioff) - return false; - - offset += wioff; - if (INT_MAX < offset) - return false; - - /* The size of the MEM_REF access determines the number of bytes. */ - tree type = TREE_TYPE (exp); - tree typesize = TYPE_SIZE_UNIT (type); - if (!typesize || !tree_fits_uhwi_p (typesize)) - return false; - nbytes = tree_to_uhwi (typesize); - if (!nbytes) - return false; - - /* Handle MEM_REF = SSA_NAME types of assignments. */ - return count_nonzero_bytes_addr (arg, offset, nbytes, lenrange, nulterm, - allnul, allnonnul, snlim); - } - - if (VAR_P (exp) || TREE_CODE (exp) == CONST_DECL) - { - /* If EXP can be folded into a constant use the result. Otherwise - proceed to use EXP to determine a range of the result. */ - if (tree fold_exp = ctor_for_folding (exp)) - if (fold_exp != error_mark_node) - exp = fold_exp; - } - - const char *prep = NULL; - if (TREE_CODE (exp) == STRING_CST) - { - unsigned nchars = TREE_STRING_LENGTH (exp); - if (nchars < offset) - return false; - - if (!nbytes) - /* If NBYTES hasn't been determined earlier, either from ADDR_EXPR - (i.e., it's the size of a pointer), or from MEM_REF (as the size - of the access), set it here to the size of the string, including - all internal and trailing nuls if the string has any. */ - nbytes = nchars - offset; - else if (nchars - offset < nbytes) - return false; - - prep = TREE_STRING_POINTER (exp) + offset; - } - - unsigned char buf[256]; - if (!prep) - { - if (CHAR_BIT != 8 || BITS_PER_UNIT != 8) - return false; - /* If the pointer to representation hasn't been set above - for STRING_CST point it at the buffer. */ - prep = reinterpret_cast <char *>(buf); - /* Try to extract the representation of the constant object - or expression starting from the offset. */ - unsigned repsize = native_encode_expr (exp, buf, sizeof buf, offset); - if (repsize < nbytes) - { - /* This should only happen when REPSIZE is zero because EXP - doesn't denote an object with a known initializer, except - perhaps when the reference reads past its end. */ - lenrange[0] = 0; - prep = NULL; - } - else if (!nbytes) - nbytes = repsize; - else if (nbytes < repsize) - return false; - } - - if (!nbytes) - return nonzero_bytes_for_type (TREE_TYPE (exp), lenrange, - nulterm, allnul, allnonnul); - - /* Compute the number of leading nonzero bytes in the representation - and update the minimum and maximum. */ - unsigned n = prep ? strnlen (prep, nbytes) : nbytes; - - if (n < lenrange[0]) - lenrange[0] = n; - if (lenrange[1] < n) - lenrange[1] = n; - - /* Set the size of the representation. */ - if (lenrange[2] < nbytes) - lenrange[2] = nbytes; - - /* Clear NULTERM if none of the bytes is zero. */ - if (n == nbytes) - *nulterm = false; - - if (n) - { - /* When the initial number of non-zero bytes N is non-zero, reset - *ALLNUL; if N is less than that the size of the representation - also clear *ALLNONNUL. */ - *allnul = false; - if (n < nbytes) - *allnonnul = false; - } - else if (*allnul || *allnonnul) - { - *allnonnul = false; - - if (*allnul) - { - /* When either ALLNUL is set and N is zero, also determine - whether all subsequent bytes after the first one (which - is nul) are zero or nonzero and clear ALLNUL if not. */ - for (const char *p = prep; p != prep + nbytes; ++p) - if (*p) - { - *allnul = false; - break; - } - } - } - - return true; -} - -/* Like count_nonzero_bytes, but instead of counting bytes in EXP, count - bytes that are pointed to by EXP, which should be a pointer. */ - -bool -strlen_pass::count_nonzero_bytes_addr (tree exp, - unsigned HOST_WIDE_INT offset, - unsigned HOST_WIDE_INT nbytes, - unsigned lenrange[3], bool *nulterm, - bool *allnul, bool *allnonnul, - ssa_name_limit_t &snlim) -{ - int idx = get_stridx (exp); - if (idx > 0) - { - strinfo *si = get_strinfo (idx); - if (!si) - return false; - - /* Handle both constant lengths as well non-constant lengths - in some range. */ - unsigned HOST_WIDE_INT minlen, maxlen; - if (tree_fits_shwi_p (si->nonzero_chars)) - minlen = maxlen = tree_to_shwi (si->nonzero_chars); - else if (si->nonzero_chars - && TREE_CODE (si->nonzero_chars) == SSA_NAME) - { - value_range vr; - ptr_qry.rvals->range_of_expr (vr, si->nonzero_chars, si->stmt); - if (vr.kind () != VR_RANGE) - return false; - - minlen = tree_to_uhwi (vr.min ()); - maxlen = tree_to_uhwi (vr.max ()); - } - else - return false; - - if (maxlen < offset) - return false; - - minlen = minlen < offset ? 0 : minlen - offset; - maxlen -= offset; - if (maxlen + 1 < nbytes) - return false; - - if (nbytes <= minlen) - *nulterm = false; - - if (nbytes < minlen) - { - minlen = nbytes; - if (nbytes < maxlen) - maxlen = nbytes; - } - - if (minlen < lenrange[0]) - lenrange[0] = minlen; - if (lenrange[1] < maxlen) - lenrange[1] = maxlen; - - if (lenrange[2] < nbytes) - lenrange[2] = nbytes; - - /* Since only the length of the string are known and not its contents, - clear ALLNUL and ALLNONNUL purely on the basis of the length. */ - *allnul = false; - if (minlen < nbytes) - *allnonnul = false; - - return true; - } - - if (TREE_CODE (exp) == ADDR_EXPR) - return count_nonzero_bytes (TREE_OPERAND (exp, 0), offset, nbytes, - lenrange, nulterm, allnul, allnonnul, snlim); - - if (TREE_CODE (exp) == SSA_NAME) - { - gimple *stmt = SSA_NAME_DEF_STMT (exp); - if (gimple_code (stmt) == GIMPLE_PHI) - { - /* Avoid processing an SSA_NAME that has already been visited - or if an SSA_NAME limit has been reached. Indicate success - if the former and failure if the latter. */ - if (int res = snlim.next_phi (exp)) - return res > 0; - - /* Determine the minimum and maximum from the PHI arguments. */ - unsigned int n = gimple_phi_num_args (stmt); - for (unsigned i = 0; i != n; i++) - { - tree def = gimple_phi_arg_def (stmt, i); - if (!count_nonzero_bytes_addr (def, offset, nbytes, lenrange, - nulterm, allnul, allnonnul, - snlim)) - return false; - } - - return true; - } - } - - /* Otherwise we don't know anything. */ - lenrange[0] = 0; - if (lenrange[1] < nbytes) - lenrange[1] = nbytes; - if (lenrange[2] < nbytes) - lenrange[2] = nbytes; - *nulterm = false; - *allnul = false; - *allnonnul = false; - return true; -} - -/* Same as above except with an implicit SSA_NAME limit. When EXPR_OR_TYPE - is a type rather than an expression use its size to compute the range. - RVALS is used to determine ranges of dynamically computed string lengths - (the results of strlen). */ - -bool -strlen_pass::count_nonzero_bytes (tree expr_or_type, - unsigned lenrange[3], bool *nulterm, - bool *allnul, bool *allnonnul) -{ - if (TYPE_P (expr_or_type)) - return nonzero_bytes_for_type (expr_or_type, lenrange, - nulterm, allnul, allnonnul); - - /* Set to optimistic values so the caller doesn't have to worry about - initializing these and to what. On success, the function will clear - these if it determines their values are different but being recursive - it never sets either to true. On failure, their values are - unspecified. */ - *nulterm = true; - *allnul = true; - *allnonnul = true; - - ssa_name_limit_t snlim; - tree expr = expr_or_type; - return count_nonzero_bytes (expr, 0, 0, lenrange, nulterm, allnul, allnonnul, - snlim); -} - -/* Handle a single or multibyte store other than by a built-in function, - either via a single character assignment or by multi-byte assignment - either via MEM_REF or via a type other than char (such as in - '*(int*)a = 12345'). Return true to let the caller advance *GSI to - the next statement in the basic block and false otherwise. */ - -bool -strlen_pass::handle_store (bool *zero_write) -{ - gimple *stmt = gsi_stmt (m_gsi); - /* The LHS and RHS of the store. The RHS is null if STMT is a function - call. STORETYPE is the type of the store (determined from either - the RHS of the assignment statement or the LHS of a function call. */ - tree lhs, rhs, storetype; - if (is_gimple_assign (stmt)) - { - lhs = gimple_assign_lhs (stmt); - rhs = gimple_assign_rhs1 (stmt); - storetype = TREE_TYPE (rhs); - } - else if (is_gimple_call (stmt)) - { - lhs = gimple_call_lhs (stmt); - rhs = NULL_TREE; - storetype = TREE_TYPE (lhs); - } - else - return true; - - tree ssaname = NULL_TREE; - strinfo *si = NULL; - int idx = -1; - - range_query *const rvals = ptr_qry.rvals; - - /* The offset of the first byte in LHS modified by the store. */ - unsigned HOST_WIDE_INT offset = 0; - - if (TREE_CODE (lhs) == MEM_REF - && TREE_CODE (TREE_OPERAND (lhs, 0)) == SSA_NAME) - { - tree mem_offset = TREE_OPERAND (lhs, 1); - if (tree_fits_uhwi_p (mem_offset)) - { - /* Get the strinfo for the base, and use it if it starts with at - least OFFSET nonzero characters. This is trivially true if - OFFSET is zero. */ - offset = tree_to_uhwi (mem_offset); - idx = get_stridx (TREE_OPERAND (lhs, 0)); - if (idx > 0) - si = get_strinfo (idx); - if (offset == 0) - ssaname = TREE_OPERAND (lhs, 0); - else if (si == NULL || compare_nonzero_chars (si, offset, rvals) < 0) - { - *zero_write = rhs ? initializer_zerop (rhs) : false; - - bool dummy; - unsigned lenrange[] = { UINT_MAX, 0, 0 }; - if (count_nonzero_bytes (rhs ? rhs : storetype, lenrange, - &dummy, &dummy, &dummy)) - maybe_warn_overflow (stmt, true, lenrange[2]); - - return true; - } - } - } - else - { - idx = get_addr_stridx (lhs, NULL_TREE, &offset, rvals); - if (idx > 0) - si = get_strinfo (idx); - } - - /* Minimum and maximum leading non-zero bytes and the size of the store. */ - unsigned lenrange[] = { UINT_MAX, 0, 0 }; - - /* Set to the minimum length of the string being assigned if known. */ - unsigned HOST_WIDE_INT rhs_minlen; - - /* STORING_NONZERO_P is true iff not all stored characters are zero. - STORING_ALL_NONZERO_P is true if all stored characters are zero. - STORING_ALL_ZEROS_P is true iff all stored characters are zero. - Both are false when it's impossible to determine which is true. */ - bool storing_nonzero_p; - bool storing_all_nonzero_p; - bool storing_all_zeros_p; - /* FULL_STRING_P is set when the stored sequence of characters form - a nul-terminated string. */ - bool full_string_p; - - const bool ranges_valid - = count_nonzero_bytes (rhs ? rhs : storetype, lenrange, &full_string_p, - &storing_all_zeros_p, &storing_all_nonzero_p); - - if (ranges_valid) - { - rhs_minlen = lenrange[0]; - storing_nonzero_p = lenrange[1] > 0; - *zero_write = storing_all_zeros_p; - - maybe_warn_overflow (stmt, true, lenrange[2]); - } - else - { - rhs_minlen = HOST_WIDE_INT_M1U; - full_string_p = false; - storing_nonzero_p = false; - storing_all_zeros_p = false; - storing_all_nonzero_p = false; - } - - if (si != NULL) - { - /* The corresponding element is set to 1 if the first and last - element, respectively, of the sequence of characters being - written over the string described by SI ends before - the terminating nul (if it has one), to zero if the nul is - being overwritten but not beyond, or negative otherwise. */ - int store_before_nul[2]; - if (ranges_valid) - { - /* The offset of the last stored byte. */ - unsigned HOST_WIDE_INT endoff = offset + lenrange[2] - 1; - store_before_nul[0] = compare_nonzero_chars (si, offset, rvals); - if (endoff == offset) - store_before_nul[1] = store_before_nul[0]; - else - store_before_nul[1] = compare_nonzero_chars (si, endoff, rvals); - } - else - { - store_before_nul[0] = compare_nonzero_chars (si, offset, rvals); - store_before_nul[1] = store_before_nul[0]; - gcc_assert (offset == 0 || store_before_nul[0] >= 0); - } - - if (storing_all_zeros_p - && store_before_nul[0] == 0 - && store_before_nul[1] == 0 - && si->full_string_p) - { - /* When overwriting a '\0' with a '\0', the store can be removed - if we know it has been stored in the current function. */ - if (!stmt_could_throw_p (cfun, stmt) && si->writable) - { - unlink_stmt_vdef (stmt); - release_defs (stmt); - gsi_remove (&m_gsi, true); - return false; - } - else - { - si->writable = true; - gsi_next (&m_gsi); - return false; - } - } - - if (store_before_nul[1] > 0 - && storing_nonzero_p - && lenrange[0] == lenrange[1] - && lenrange[0] == lenrange[2] - && TREE_CODE (storetype) == INTEGER_TYPE) - { - /* Handle a store of one or more non-nul characters that ends - before the terminating nul of the destination and so does - not affect its length - If si->nonzero_chars > OFFSET, we aren't overwriting '\0', - and if we aren't storing '\0', we know that the length of - the string and any other zero terminated string in memory - remains the same. In that case we move to the next gimple - statement and return to signal the caller that it shouldn't - invalidate anything. - - This is beneficial for cases like: - - char p[20]; - void foo (char *q) - { - strcpy (p, "foobar"); - size_t len = strlen (p); // can be folded to 6 - size_t len2 = strlen (q); // has to be computed - p[0] = 'X'; - size_t len3 = strlen (p); // can be folded to 6 - size_t len4 = strlen (q); // can be folded to len2 - bar (len, len2, len3, len4); - } */ - gsi_next (&m_gsi); - return false; - } - - if (storing_all_zeros_p - || storing_nonzero_p - || (offset != 0 && store_before_nul[1] > 0)) - { - /* When STORING_NONZERO_P, we know that the string will start - with at least OFFSET + 1 nonzero characters. If storing - a single character, set si->NONZERO_CHARS to the result. - If storing multiple characters, try to determine the number - of leading non-zero characters and set si->NONZERO_CHARS to - the result instead. - - When STORING_ALL_ZEROS_P, we know that the string is now - OFFSET characters long. - - Otherwise, we're storing an unknown value at offset OFFSET, - so need to clip the nonzero_chars to OFFSET. - Use the minimum length of the string (or individual character) - being stored if it's known. Otherwise, STORING_NONZERO_P - guarantees it's at least 1. */ - HOST_WIDE_INT len - = storing_nonzero_p && ranges_valid ? lenrange[0] : 1; - location_t loc = gimple_location (stmt); - tree oldlen = si->nonzero_chars; - if (store_before_nul[1] == 0 && si->full_string_p) - /* We're overwriting the nul terminator with a nonzero or - unknown character. If the previous stmt was a memcpy, - its length may be decreased. */ - adjust_last_stmt (si, stmt, false); - si = unshare_strinfo (si); - if (storing_nonzero_p) - { - gcc_assert (len >= 0); - si->nonzero_chars = build_int_cst (size_type_node, offset + len); - } - else - si->nonzero_chars = build_int_cst (size_type_node, offset); - - /* Set FULL_STRING_P only if the length of the strings being - written is the same, and clear it if the strings have - different lengths. In the latter case the length stored - in si->NONZERO_CHARS becomes the lower bound. - FIXME: Handle the upper bound of the length if possible. */ - si->full_string_p = full_string_p && lenrange[0] == lenrange[1]; - - if (storing_all_zeros_p - && ssaname - && !SSA_NAME_OCCURS_IN_ABNORMAL_PHI (ssaname)) - si->endptr = ssaname; - else - si->endptr = NULL; - si->next = 0; - si->stmt = NULL; - si->writable = true; - si->dont_invalidate = true; - if (oldlen) - { - tree adj = fold_build2_loc (loc, MINUS_EXPR, size_type_node, - si->nonzero_chars, oldlen); - adjust_related_strinfos (loc, si, adj); - } - else - si->prev = 0; - } - } - else if (idx == 0 && (storing_all_zeros_p || storing_nonzero_p)) - { - if (ssaname) - idx = new_stridx (ssaname); - else - idx = new_addr_stridx (lhs); - if (idx != 0) - { - tree ptr = (ssaname ? ssaname : build_fold_addr_expr (lhs)); - - HOST_WIDE_INT slen; - if (storing_all_zeros_p) - slen = 0; - else if (storing_nonzero_p && ranges_valid) - { - /* FIXME: Handle the upper bound of the length when - LENRANGE[0] != LENRANGE[1]. */ - slen = lenrange[0]; - if (lenrange[0] != lenrange[1]) - /* Set the minimum length but ignore the maximum - for now. */ - full_string_p = false; - } - else - slen = -1; - - tree len = (slen <= 0 - ? size_zero_node - : build_int_cst (size_type_node, slen)); - si = new_strinfo (ptr, idx, len, slen >= 0 && full_string_p); - set_strinfo (idx, si); - if (storing_all_zeros_p - && ssaname - && !SSA_NAME_OCCURS_IN_ABNORMAL_PHI (ssaname)) - si->endptr = ssaname; - si->dont_invalidate = true; - si->writable = true; - } - } - else if (idx == 0 - && rhs_minlen < HOST_WIDE_INT_M1U - && ssaname == NULL_TREE - && TREE_CODE (TREE_TYPE (lhs)) == ARRAY_TYPE) - { - HOST_WIDE_INT a = int_size_in_bytes (TREE_TYPE (lhs)); - if (a > 0 && (unsigned HOST_WIDE_INT) a > rhs_minlen) - { - int idx = new_addr_stridx (lhs); - if (idx != 0) - { - si = new_strinfo (build_fold_addr_expr (lhs), idx, - build_int_cst (size_type_node, rhs_minlen), - full_string_p); - set_strinfo (idx, si); - si->dont_invalidate = true; - } - } - } - - if (si != NULL && offset == 0 && storing_all_zeros_p && lenrange[2] == 1) - { - /* For single-byte stores only, allow adjust_last_stmt to remove - the statement if the stored '\0' is immediately overwritten. */ - laststmt.stmt = stmt; - laststmt.len = build_int_cst (size_type_node, 1); - laststmt.stridx = si->idx; - } - return true; -} - -/* Try to fold strstr (s, t) eq/ne s to strncmp (s, t, strlen (t)) eq/ne 0. */ - -static void -fold_strstr_to_strncmp (tree rhs1, tree rhs2, gimple *stmt) -{ - if (TREE_CODE (rhs1) != SSA_NAME - || TREE_CODE (rhs2) != SSA_NAME) - return; - - gimple *call_stmt = NULL; - for (int pass = 0; pass < 2; pass++) - { - gimple *g = SSA_NAME_DEF_STMT (rhs1); - if (gimple_call_builtin_p (g, BUILT_IN_STRSTR) - && has_single_use (rhs1) - && gimple_call_arg (g, 0) == rhs2) - { - call_stmt = g; - break; - } - std::swap (rhs1, rhs2); - } - - if (call_stmt) - { - tree arg0 = gimple_call_arg (call_stmt, 0); - - if (arg0 == rhs2) - { - tree arg1 = gimple_call_arg (call_stmt, 1); - tree arg1_len = NULL_TREE; - int idx = get_stridx (arg1); - - if (idx) - { - if (idx < 0) - arg1_len = build_int_cst (size_type_node, ~idx); - else - { - strinfo *si = get_strinfo (idx); - if (si) - arg1_len = get_string_length (si); - } - } - - if (arg1_len != NULL_TREE) - { - gimple_stmt_iterator gsi = gsi_for_stmt (call_stmt); - tree strncmp_decl = builtin_decl_explicit (BUILT_IN_STRNCMP); - - if (!is_gimple_val (arg1_len)) - { - tree arg1_len_tmp = make_ssa_name (TREE_TYPE (arg1_len)); - gassign *arg1_stmt = gimple_build_assign (arg1_len_tmp, - arg1_len); - gsi_insert_before (&gsi, arg1_stmt, GSI_SAME_STMT); - arg1_len = arg1_len_tmp; - } - - gcall *strncmp_call = gimple_build_call (strncmp_decl, 3, - arg0, arg1, arg1_len); - tree strncmp_lhs = make_ssa_name (integer_type_node); - gimple_set_vuse (strncmp_call, gimple_vuse (call_stmt)); - gimple_call_set_lhs (strncmp_call, strncmp_lhs); - gsi_remove (&gsi, true); - gsi_insert_before (&gsi, strncmp_call, GSI_SAME_STMT); - tree zero = build_zero_cst (TREE_TYPE (strncmp_lhs)); - - if (is_gimple_assign (stmt)) - { - if (gimple_assign_rhs_code (stmt) == COND_EXPR) - { - tree cond = gimple_assign_rhs1 (stmt); - TREE_OPERAND (cond, 0) = strncmp_lhs; - TREE_OPERAND (cond, 1) = zero; - } - else - { - gimple_assign_set_rhs1 (stmt, strncmp_lhs); - gimple_assign_set_rhs2 (stmt, zero); - } - } - else - { - gcond *cond = as_a<gcond *> (stmt); - gimple_cond_set_lhs (cond, strncmp_lhs); - gimple_cond_set_rhs (cond, zero); - } - update_stmt (stmt); - } - } - } -} - -/* Return true if TYPE corresponds to a narrow character type. */ - -static bool -is_char_type (tree type) -{ - return (TREE_CODE (type) == INTEGER_TYPE - && TYPE_MODE (type) == TYPE_MODE (char_type_node) - && TYPE_PRECISION (type) == TYPE_PRECISION (char_type_node)); -} - -/* Check the built-in call at GSI for validity and optimize it. - Uses RVALS to determine range information. - Return true to let the caller advance *GSI to the next statement - in the basic block and false otherwise. */ - -bool -strlen_pass::check_and_optimize_call (bool *zero_write) -{ - gimple *stmt = gsi_stmt (m_gsi); - - if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) - { - tree fntype = gimple_call_fntype (stmt); - if (!fntype) - return true; - - if (lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (fntype))) - { - handle_alloc_call (BUILT_IN_NONE); - return true; - } - - if (tree lhs = gimple_call_lhs (stmt)) - handle_assign (lhs, zero_write); - - /* Proceed to handle user-defined formatting functions. */ - } - - /* When not optimizing we must be checking printf calls which - we do even for user-defined functions when they are declared - with attribute format. */ - if (!flag_optimize_strlen - || !strlen_optimize - || !valid_builtin_call (stmt)) - return !handle_printf_call (&m_gsi, ptr_qry); - - tree callee = gimple_call_fndecl (stmt); - switch (DECL_FUNCTION_CODE (callee)) - { - case BUILT_IN_STRLEN: - case BUILT_IN_STRNLEN: - handle_builtin_strlen (); - break; - case BUILT_IN_STRCHR: - handle_builtin_strchr (); - break; - case BUILT_IN_STRCPY: - case BUILT_IN_STRCPY_CHK: - case BUILT_IN_STPCPY: - case BUILT_IN_STPCPY_CHK: - handle_builtin_strcpy (DECL_FUNCTION_CODE (callee)); - break; - - case BUILT_IN_STRNCAT: - case BUILT_IN_STRNCAT_CHK: - handle_builtin_strncat (DECL_FUNCTION_CODE (callee)); - break; - - case BUILT_IN_STPNCPY: - case BUILT_IN_STPNCPY_CHK: - case BUILT_IN_STRNCPY: - case BUILT_IN_STRNCPY_CHK: - handle_builtin_stxncpy_strncat (false); - break; - - case BUILT_IN_MEMCPY: - case BUILT_IN_MEMCPY_CHK: - case BUILT_IN_MEMPCPY: - case BUILT_IN_MEMPCPY_CHK: - handle_builtin_memcpy (DECL_FUNCTION_CODE (callee)); - break; - case BUILT_IN_STRCAT: - case BUILT_IN_STRCAT_CHK: - handle_builtin_strcat (DECL_FUNCTION_CODE (callee)); - break; - case BUILT_IN_ALLOCA: - case BUILT_IN_ALLOCA_WITH_ALIGN: - case BUILT_IN_MALLOC: - case BUILT_IN_CALLOC: - handle_alloc_call (DECL_FUNCTION_CODE (callee)); - break; - case BUILT_IN_MEMSET: - if (handle_builtin_memset (zero_write)) - return false; - break; - case BUILT_IN_MEMCMP: - if (handle_builtin_memcmp ()) - return false; - break; - case BUILT_IN_STRCMP: - case BUILT_IN_STRNCMP: - if (handle_builtin_string_cmp ()) - return false; - break; - default: - if (handle_printf_call (&m_gsi, ptr_qry)) - return false; - break; - } - - return true; -} - -/* Handle an assignment statement at *GSI to a LHS of integral type. - If GSI's basic block needs clean-up of EH, set *CLEANUP_EH to true. */ - -void -strlen_pass::handle_integral_assign (bool *cleanup_eh) -{ - gimple *stmt = gsi_stmt (m_gsi); - tree lhs = gimple_assign_lhs (stmt); - tree lhs_type = TREE_TYPE (lhs); - - enum tree_code code = gimple_assign_rhs_code (stmt); - if (code == COND_EXPR) - { - tree cond = gimple_assign_rhs1 (stmt); - enum tree_code cond_code = TREE_CODE (cond); - - if (cond_code == EQ_EXPR || cond_code == NE_EXPR) - fold_strstr_to_strncmp (TREE_OPERAND (cond, 0), - TREE_OPERAND (cond, 1), stmt); - } - else if (code == EQ_EXPR || code == NE_EXPR) - fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt), - gimple_assign_rhs2 (stmt), stmt); - else if (gimple_assign_load_p (stmt) - && TREE_CODE (lhs_type) == INTEGER_TYPE - && TYPE_MODE (lhs_type) == TYPE_MODE (char_type_node) - && (TYPE_PRECISION (lhs_type) - == TYPE_PRECISION (char_type_node)) - && !gimple_has_volatile_ops (stmt)) - { - tree off = integer_zero_node; - unsigned HOST_WIDE_INT coff = 0; - int idx = 0; - tree rhs1 = gimple_assign_rhs1 (stmt); - if (code == MEM_REF) - { - idx = get_stridx (TREE_OPERAND (rhs1, 0)); - if (idx > 0) - { - strinfo *si = get_strinfo (idx); - if (si - && si->nonzero_chars - && TREE_CODE (si->nonzero_chars) == INTEGER_CST - && (wi::to_widest (si->nonzero_chars) - >= wi::to_widest (off))) - off = TREE_OPERAND (rhs1, 1); - else - /* This case is not useful. See if get_addr_stridx - returns something usable. */ - idx = 0; - } - } - if (idx <= 0) - idx = get_addr_stridx (rhs1, NULL_TREE, &coff); - if (idx > 0) - { - strinfo *si = get_strinfo (idx); - if (si - && si->nonzero_chars - && TREE_CODE (si->nonzero_chars) == INTEGER_CST) - { - widest_int w1 = wi::to_widest (si->nonzero_chars); - widest_int w2 = wi::to_widest (off) + coff; - if (w1 == w2 - && si->full_string_p) - { - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "Optimizing: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - - /* Reading the final '\0' character. */ - tree zero = build_int_cst (lhs_type, 0); - gimple_set_vuse (stmt, NULL_TREE); - gimple_assign_set_rhs_from_tree (&m_gsi, zero); - *cleanup_eh - |= maybe_clean_or_replace_eh_stmt (stmt, - gsi_stmt (m_gsi)); - stmt = gsi_stmt (m_gsi); - update_stmt (stmt); - - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "into: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - } - else if (w1 > w2) - { - /* Reading a character before the final '\0' - character. Just set the value range to ~[0, 0] - if we don't have anything better. */ - value_range r; - if (!get_range_query (cfun)->range_of_expr (r, lhs) - || r.varying_p ()) - { - r.set_nonzero (lhs_type); - set_range_info (lhs, r); - } - } - } - } - } - else if (code == MEM_REF && TREE_CODE (lhs) == SSA_NAME) - { - if (int idx = new_stridx (lhs)) - { - /* Record multi-byte assignments from MEM_REFs. */ - bool storing_all_nonzero_p; - bool storing_all_zeros_p; - bool full_string_p; - unsigned lenrange[] = { UINT_MAX, 0, 0 }; - tree rhs = gimple_assign_rhs1 (stmt); - const bool ranges_valid - = count_nonzero_bytes (rhs, lenrange, &full_string_p, - &storing_all_zeros_p, - &storing_all_nonzero_p); - if (ranges_valid) - { - tree length = build_int_cst (sizetype, lenrange[0]); - strinfo *si = new_strinfo (lhs, idx, length, full_string_p); - set_strinfo (idx, si); - si->writable = true; - si->dont_invalidate = true; - } - } - } - - if (strlen_to_stridx) - { - tree rhs1 = gimple_assign_rhs1 (stmt); - if (stridx_strlenloc *ps = strlen_to_stridx->get (rhs1)) - strlen_to_stridx->put (lhs, stridx_strlenloc (*ps)); - } -} - -/* Handle assignment statement at *GSI to LHS. Set *ZERO_WRITE if - the assignent stores all zero bytes.. */ - -bool -strlen_pass::handle_assign (tree lhs, bool *zero_write) -{ - tree type = TREE_TYPE (lhs); - if (TREE_CODE (type) == ARRAY_TYPE) - type = TREE_TYPE (type); - - bool is_char_store = is_char_type (type); - if (!is_char_store && TREE_CODE (lhs) == MEM_REF) - { - /* To consider stores into char objects via integer types other - than char but not those to non-character objects, determine - the type of the destination rather than just the type of - the access. */ - for (int i = 0; i != 2; ++i) - { - tree ref = TREE_OPERAND (lhs, i); - type = TREE_TYPE (ref); - if (TREE_CODE (type) == POINTER_TYPE) - type = TREE_TYPE (type); - if (TREE_CODE (type) == ARRAY_TYPE) - type = TREE_TYPE (type); - if (is_char_type (type)) - { - is_char_store = true; - break; - } - } - } - - /* Handle a single or multibyte assignment. */ - if (is_char_store && !handle_store (zero_write)) - return false; - - return true; -} - - -/* Attempt to check for validity of the performed access a single statement - at *GSI using string length knowledge, and to optimize it. - If the given basic block needs clean-up of EH, CLEANUP_EH is set to - true. Return true to let the caller advance *GSI to the next statement - in the basic block and false otherwise. */ - -bool -strlen_pass::check_and_optimize_stmt (bool *cleanup_eh) -{ - gimple *stmt = gsi_stmt (m_gsi); - - /* For statements that modify a string, set to true if the write - is only zeros. */ - bool zero_write = false; - - if (is_gimple_call (stmt)) - { - if (!check_and_optimize_call (&zero_write)) - return false; - } - else if (!flag_optimize_strlen || !strlen_optimize) - return true; - else if (is_gimple_assign (stmt) && !gimple_clobber_p (stmt)) - { - /* Handle non-clobbering assignment. */ - tree lhs = gimple_assign_lhs (stmt); - tree lhs_type = TREE_TYPE (lhs); - - if (TREE_CODE (lhs) == SSA_NAME && POINTER_TYPE_P (lhs_type)) - { - if (gimple_assign_single_p (stmt) - || (gimple_assign_cast_p (stmt) - && POINTER_TYPE_P (TREE_TYPE (gimple_assign_rhs1 (stmt))))) - { - int idx = get_stridx (gimple_assign_rhs1 (stmt)); - ssa_ver_to_stridx[SSA_NAME_VERSION (lhs)] = idx; - } - else if (gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR) - handle_pointer_plus (); - } - else if (TREE_CODE (lhs) == SSA_NAME && INTEGRAL_TYPE_P (lhs_type)) - /* Handle assignment to a character. */ - handle_integral_assign (cleanup_eh); - else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs)) - if (!handle_assign (lhs, &zero_write)) - return false; - } - else if (gcond *cond = dyn_cast<gcond *> (stmt)) - { - enum tree_code code = gimple_cond_code (cond); - if (code == EQ_EXPR || code == NE_EXPR) - fold_strstr_to_strncmp (gimple_cond_lhs (stmt), - gimple_cond_rhs (stmt), stmt); - } - - if (gimple_vdef (stmt)) - maybe_invalidate (stmt, zero_write); - return true; -} - -/* Recursively call maybe_invalidate on stmts that might be executed - in between dombb and current bb and that contain a vdef. Stop when - *count stmts are inspected, or if the whole strinfo vector has - been invalidated. */ - -static void -do_invalidate (basic_block dombb, gimple *phi, bitmap visited, int *count) -{ - unsigned int i, n = gimple_phi_num_args (phi); - - for (i = 0; i < n; i++) - { - tree vuse = gimple_phi_arg_def (phi, i); - gimple *stmt = SSA_NAME_DEF_STMT (vuse); - basic_block bb = gimple_bb (stmt); - if (bb == NULL - || bb == dombb - || !bitmap_set_bit (visited, bb->index) - || !dominated_by_p (CDI_DOMINATORS, bb, dombb)) - continue; - while (1) - { - if (gimple_code (stmt) == GIMPLE_PHI) - { - do_invalidate (dombb, stmt, visited, count); - if (*count == 0) - return; - break; - } - if (--*count == 0) - return; - if (!maybe_invalidate (stmt)) - { - *count = 0; - return; - } - vuse = gimple_vuse (stmt); - stmt = SSA_NAME_DEF_STMT (vuse); - if (gimple_bb (stmt) != bb) - { - bb = gimple_bb (stmt); - if (bb == NULL - || bb == dombb - || !bitmap_set_bit (visited, bb->index) - || !dominated_by_p (CDI_DOMINATORS, bb, dombb)) - break; - } - } - } -} - -/* Release pointer_query cache. */ - -strlen_pass::~strlen_pass () -{ - ptr_qry.flush_cache (); -} - -/* Callback for walk_dominator_tree. Attempt to optimize various - string ops by remembering string lengths pointed by pointer SSA_NAMEs. */ - -edge -strlen_pass::before_dom_children (basic_block bb) -{ - evrp.enter (bb); - - basic_block dombb = get_immediate_dominator (CDI_DOMINATORS, bb); - - if (dombb == NULL) - stridx_to_strinfo = NULL; - else - { - stridx_to_strinfo = ((vec<strinfo *, va_heap, vl_embed> *) dombb->aux); - if (stridx_to_strinfo) - { - for (gphi_iterator gsi = gsi_start_phis (bb); !gsi_end_p (gsi); - gsi_next (&gsi)) - { - gphi *phi = gsi.phi (); - if (virtual_operand_p (gimple_phi_result (phi))) - { - bitmap visited = BITMAP_ALLOC (NULL); - int count_vdef = 100; - do_invalidate (dombb, phi, visited, &count_vdef); - BITMAP_FREE (visited); - if (count_vdef == 0) - { - /* If there were too many vdefs in between immediate - dominator and current bb, invalidate everything. - If stridx_to_strinfo has been unshared, we need - to free it, otherwise just set it to NULL. */ - if (!strinfo_shared ()) - { - unsigned int i; - strinfo *si; - - for (i = 1; - vec_safe_iterate (stridx_to_strinfo, i, &si); - ++i) - { - free_strinfo (si); - (*stridx_to_strinfo)[i] = NULL; - } - } - else - stridx_to_strinfo = NULL; - } - break; - } - } - } - } - - /* If all PHI arguments have the same string index, the PHI result - has it as well. */ - for (gphi_iterator gsi = gsi_start_phis (bb); !gsi_end_p (gsi); - gsi_next (&gsi)) - { - gphi *phi = gsi.phi (); - tree result = gimple_phi_result (phi); - if (!virtual_operand_p (result) && POINTER_TYPE_P (TREE_TYPE (result))) - { - int idx = get_stridx (gimple_phi_arg_def (phi, 0)); - if (idx != 0) - { - unsigned int i, n = gimple_phi_num_args (phi); - for (i = 1; i < n; i++) - if (idx != get_stridx (gimple_phi_arg_def (phi, i))) - break; - if (i == n) - ssa_ver_to_stridx[SSA_NAME_VERSION (result)] = idx; - } - } - } - - bool cleanup_eh = false; - - /* Attempt to optimize individual statements. */ - for (m_gsi = gsi_start_bb (bb); !gsi_end_p (m_gsi); ) - { - gimple *stmt = gsi_stmt (m_gsi); - - /* First record ranges generated by this statement so they - can be used by printf argument processing. */ - evrp.record_ranges_from_stmt (stmt, false); - - /* Reset search depth preformance counter. */ - ptr_qry.depth = 0; - - if (check_and_optimize_stmt (&cleanup_eh)) - gsi_next (&m_gsi); - } - - if (cleanup_eh && gimple_purge_dead_eh_edges (bb)) - m_cleanup_cfg = true; - - bb->aux = stridx_to_strinfo; - if (vec_safe_length (stridx_to_strinfo) && !strinfo_shared ()) - (*stridx_to_strinfo)[0] = (strinfo *) bb; - return NULL; -} - -/* Callback for walk_dominator_tree. Free strinfo vector if it is - owned by the current bb, clear bb->aux. */ - -void -strlen_pass::after_dom_children (basic_block bb) -{ - evrp.leave (bb); - - if (bb->aux) - { - stridx_to_strinfo = ((vec<strinfo *, va_heap, vl_embed> *) bb->aux); - if (vec_safe_length (stridx_to_strinfo) - && (*stridx_to_strinfo)[0] == (strinfo *) bb) - { - unsigned int i; - strinfo *si; - - for (i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i) - free_strinfo (si); - vec_free (stridx_to_strinfo); - } - bb->aux = NULL; - } -} - -namespace { - -static unsigned int -printf_strlen_execute (function *fun, bool warn_only) -{ - strlen_optimize = !warn_only; - - calculate_dominance_info (CDI_DOMINATORS); - - bool use_scev = optimize > 0 && flag_printf_return_value; - if (use_scev) - { - loop_optimizer_init (LOOPS_NORMAL); - scev_initialize (); - } - - gcc_assert (!strlen_to_stridx); - if (warn_stringop_overflow || warn_stringop_truncation) - strlen_to_stridx = new hash_map<tree, stridx_strlenloc> (); - - /* This has to happen after initializing the loop optimizer - and initializing SCEV as they create new SSA_NAMEs. */ - ssa_ver_to_stridx.safe_grow_cleared (num_ssa_names, true); - max_stridx = 1; - - /* String length optimization is implemented as a walk of the dominator - tree and a forward walk of statements within each block. */ - strlen_pass walker (CDI_DOMINATORS); - walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun)); - - if (dump_file && (dump_flags & TDF_DETAILS)) - walker.ptr_qry.dump (dump_file); - - ssa_ver_to_stridx.release (); - strinfo_pool.release (); - if (decl_to_stridxlist_htab) - { - obstack_free (&stridx_obstack, NULL); - delete decl_to_stridxlist_htab; - decl_to_stridxlist_htab = NULL; - } - laststmt.stmt = NULL; - laststmt.len = NULL_TREE; - laststmt.stridx = 0; - - if (strlen_to_stridx) - { - strlen_to_stridx->empty (); - delete strlen_to_stridx; - strlen_to_stridx = NULL; - } - - if (use_scev) - { - scev_finalize (); - loop_optimizer_finalize (); - } - - return walker.m_cleanup_cfg ? TODO_cleanup_cfg : 0; -} - -/* This file defines two passes: one for warnings that runs only when - optimization is disabled, and another that implements optimizations - and also issues warnings. */ - -const pass_data pass_data_warn_printf = -{ - GIMPLE_PASS, /* type */ - "warn-printf", /* name */ - OPTGROUP_NONE, /* optinfo_flags */ - TV_NONE, /* tv_id */ - /* Normally an optimization pass would require PROP_ssa but because - this pass runs early, with no optimization, to do sprintf format - checking, it only requires PROP_cfg. */ - PROP_cfg, /* properties_required */ - 0, /* properties_provided */ - 0, /* properties_destroyed */ - 0, /* todo_flags_start */ - 0, /* todo_flags_finish */ -}; - -class pass_warn_printf : public gimple_opt_pass -{ -public: - pass_warn_printf (gcc::context *ctxt) - : gimple_opt_pass (pass_data_warn_printf, ctxt) - {} - - virtual bool gate (function *); - virtual unsigned int execute (function *fun) - { - return printf_strlen_execute (fun, true); - } -}; - - -/* Return true to run the warning pass only when not optimizing and - iff either -Wformat-overflow or -Wformat-truncation is specified. */ - -bool -pass_warn_printf::gate (function *) -{ - return !optimize && (warn_format_overflow > 0 || warn_format_trunc > 0); -} - -const pass_data pass_data_strlen = -{ - GIMPLE_PASS, /* type */ - "strlen", /* name */ - OPTGROUP_NONE, /* optinfo_flags */ - TV_TREE_STRLEN, /* tv_id */ - PROP_cfg | PROP_ssa, /* properties_required */ - 0, /* properties_provided */ - 0, /* properties_destroyed */ - 0, /* todo_flags_start */ - 0, /* todo_flags_finish */ -}; - -class pass_strlen : public gimple_opt_pass -{ -public: - pass_strlen (gcc::context *ctxt) - : gimple_opt_pass (pass_data_strlen, ctxt) - {} - - opt_pass * clone () { return new pass_strlen (m_ctxt); } - - virtual bool gate (function *); - virtual unsigned int execute (function *fun) - { - return printf_strlen_execute (fun, false); - } -}; - -/* Return true to run the pass only when the sprintf and/or strlen - optimizations are enabled and -Wformat-overflow or -Wformat-truncation - are specified. */ - -bool -pass_strlen::gate (function *) -{ - return ((warn_format_overflow > 0 - || warn_format_trunc > 0 - || warn_restrict > 0 - || flag_optimize_strlen > 0 - || flag_printf_return_value) - && optimize > 0); -} - -} // anon namespace - -gimple_opt_pass * -make_pass_warn_printf (gcc::context *ctxt) -{ - return new pass_warn_printf (ctxt); -} - -gimple_opt_pass * -make_pass_strlen (gcc::context *ctxt) -{ - return new pass_strlen (ctxt); -} |