| /* 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[ |