diff options
author | Martin Sebor <msebor@redhat.com> | 2019-12-11 19:50:43 +0000 |
---|---|---|
committer | Martin Sebor <msebor@gcc.gnu.org> | 2019-12-11 12:50:43 -0700 |
commit | f7d86b5ca830ca95899ec5e1585359f9baf19238 (patch) | |
tree | 2ccb64ec581e259d048fcc8de48cee51fbf37494 /gcc/tree-ssa-strlen.c | |
parent | c7f5b4eddd8b11383074876e45863ae6b92a1357 (diff) | |
download | gcc-f7d86b5ca830ca95899ec5e1585359f9baf19238.zip gcc-f7d86b5ca830ca95899ec5e1585359f9baf19238.tar.gz gcc-f7d86b5ca830ca95899ec5e1585359f9baf19238.tar.bz2 |
builtins.c (compute_objsize): Add an argument and set it to offset into destination.
gcc/ChangeLog:
* builtins.c (compute_objsize): Add an argument and set it to offset
into destination.
* builtins.h (compute_objsize): Add an argument.
* tree-object-size.c (addr_object_size): Add an argument and set it
to offset into destination.
(compute_builtin_object_size): Same.
* tree-object-size.h (compute_builtin_object_size): Add an argument.
* tree-ssa-strlen.c (get_addr_stridx): Add an argument and set it
to offset into destination.
(maybe_warn_overflow): New function.
(handle_store): Call maybe_warn_overflow to issue warnings.
gcc/testsuite/ChangeLog:
* c-c++-common/Wstringop-overflow-2.c: Adjust text of expected messages.
* g++.dg/warn/Wstringop-overflow-3.C: Same.
* gcc.dg/Wstringop-overflow-17.c: Same.
From-SVN: r279248
Diffstat (limited to 'gcc/tree-ssa-strlen.c')
-rw-r--r-- | gcc/tree-ssa-strlen.c | 445 |
1 files changed, 389 insertions, 56 deletions
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index beff17b..212ac715 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -188,6 +188,49 @@ struct laststmt_struct static int get_stridx_plus_constant (strinfo *, unsigned HOST_WIDE_INT, tree); static void handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *); +/* Sets MINMAX to either the constant value or the range VAL is in + and returns true on success. When nonnull, uses RVALS to get + VAL's range. Otherwise uses get_range_info. */ + +static bool +get_range (tree val, wide_int minmax[2], const vr_values *rvals = NULL) +{ + if (tree_fits_uhwi_p (val)) + { + minmax[0] = minmax[1] = wi::to_wide (val); + return true; + } + + if (TREE_CODE (val) != SSA_NAME) + return false; + + if (rvals) + { + /* The range below may be "inaccurate" if a constant has been + substituted earlier for VAL by this pass that hasn't been + propagated through the CFG. This shoud be fixed by the new + on-demand VRP if/when it becomes available (hopefully in + GCC 11). */ + const value_range *vr + = (CONST_CAST (class vr_values *, rvals)->get_value_range (val)); + value_range_kind rng = vr->kind (); + if (rng != VR_RANGE || !range_int_cst_p (vr)) + return false; + + minmax[0] = wi::to_wide (vr->min ()); + minmax[1] = wi::to_wide (vr->max ()); + return true; + } + + value_range_kind rng = get_range_info (val, minmax, minmax + 1); + if (rng == VR_RANGE) + return true; + + /* Do not handle anti-ranges and instead make use of the on-demand + VRP if/when it becomes available (hopefully in GCC 11). */ + return false; +} + /* Return: * +1 if SI is known to start with more than OFF nonzero characters. @@ -333,24 +376,32 @@ get_addr_stridx (tree exp, tree ptr, unsigned HOST_WIDE_INT *offset_out, return 0; } -/* Return string index for EXP. */ +/* 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. */ static int -get_stridx (tree exp) +get_stridx (tree exp, wide_int offrng[2] = NULL) { + if (offrng) + offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (sizetype)); + 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 0; + return last_idx; tree_code rhs_code = gimple_assign_rhs_code (def_stmt); tree ptr, off; @@ -402,25 +453,69 @@ get_stridx (tree exp) else return 0; - if (TREE_CODE (ptr) != SSA_NAME - || !tree_fits_shwi_p (off)) + 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, offrng)) + { + /* 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 0; + return last_idx; + offset = (unsigned HOST_WIDE_INT) offset + this_off; if (offset < 0) - return 0; - if (ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)]) + return last_idx; + + if (int idx = ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)]) { - strinfo *si - = get_strinfo (ssa_ver_to_stridx[SSA_NAME_VERSION (ptr)]); - if (si && compare_nonzero_chars (si, offset) >= 0) - return get_stridx_plus_constant (si, offset, exp); + 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 0; + + return last_idx; } if (TREE_CODE (exp) == ADDR_EXPR) @@ -1762,6 +1857,279 @@ maybe_set_strlen_range (tree lhs, tree src, tree bound) return set_strlen_range (lhs, min, max, bound); } +/* Diagnose buffer overflow by a STMT writing LEN + PLUS_ONE bytes, + into an object designated by the LHS of STMT otherise. */ + +static void +maybe_warn_overflow (gimple *stmt, tree len, + const vr_values *rvals = NULL, + strinfo *si = NULL, bool plus_one = false) +{ + if (!len || gimple_no_warning_p (stmt)) + return; + + tree writefn = NULL_TREE; + tree destdecl = NULL_TREE; + tree destsize = NULL_TREE; + tree dest = NULL_TREE; + + /* The offset into the destination object set by compute_objsize + but already reflected in DESTSIZE. */ + tree destoff = NULL_TREE; + + if (is_gimple_assign (stmt)) + { + dest = gimple_assign_lhs (stmt); + if (TREE_NO_WARNING (dest)) + return; + + /* For assignments try to determine the size of the destination + first. Set DESTOFF to the the offset on success. */ + tree off = size_zero_node; + destsize = compute_objsize (dest, 1, &destdecl, &off); + if (destsize) + destoff = off; + } + else if (is_gimple_call (stmt)) + { + writefn = gimple_call_fndecl (stmt); + dest = gimple_call_arg (stmt, 0); + } + + /* The offset into the destination object computed below and not + reflected in DESTSIZE. Either DESTOFF is set above or OFFRNG + below. */ + wide_int offrng[2]; + offrng[0] = wi::zero (TYPE_PRECISION (sizetype)); + offrng[1] = offrng[0]; + + if (!destsize && !si && dest) + { + /* For both assignments and calls, if no destination STRINFO was + provided, try to get it from the DEST. */ + tree ref = dest; + tree off = NULL_TREE; + if (TREE_CODE (ref) == ARRAY_REF) + { + /* Handle stores to VLAs (represented as + ARRAY_REF (MEM_REF (vlaptr, 0), N]. */ + off = TREE_OPERAND (ref, 1); + ref = TREE_OPERAND (ref, 0); + } + + if (TREE_CODE (ref) == MEM_REF) + { + tree mem_off = TREE_OPERAND (ref, 1); + if (off) + { + if (!integer_zerop (mem_off)) + return; + } + else + off = mem_off; + ref = TREE_OPERAND (ref, 0); + } + + if (int idx = get_stridx (ref, offrng)) + { + si = get_strinfo (idx); + if (off && TREE_CODE (off) == INTEGER_CST) + { + wide_int wioff = wi::to_wide (off, offrng->get_precision ()); + offrng[0] += wioff; + offrng[1] += wioff; + } + } + else + return; + } + + /* 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 && offrng[0] == 0 && offrng[0] == offrng[1]) + return; + + wide_int lenrng[2]; + if (!get_range (len, lenrng, rvals)) + return; + + if (plus_one) + { + lenrng[0] += 1; + lenrng[1] += 1; + } + + /* Compute the range of sizes of the destination object. The range + is constant for declared objects but may be a range for allocated + objects. */ + wide_int sizrng[2]; + if (!destsize || !get_range (destsize, sizrng, rvals)) + { + /* On failure, rather than bailing outright, use the maximum range + so that overflow in allocated objects whose size depends on + the strlen of the source can still be diagnosed below. */ + sizrng[0] = wi::zero (lenrng->get_precision ()); + sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)); + } + + /* The size of the remaining space in the destination computed as + the size of the latter minus the offset into it. */ + wide_int spcrng[2] = { sizrng[0], sizrng[1] }; + if (wi::sign_mask (offrng[0])) + { + /* FIXME: Handle negative offsets into allocated objects. */ + if (destdecl) + spcrng[0] = spcrng[1] = wi::zero (spcrng->get_precision ()); + else + return; + } + else + { + spcrng[0] -= wi::ltu_p (offrng[0], spcrng[0]) ? offrng[0] : spcrng[0]; + spcrng[1] -= wi::ltu_p (offrng[0], spcrng[1]) ? offrng[0] : spcrng[1]; + } + + if (wi::leu_p (lenrng[0], spcrng[0])) + return; + + if (lenrng[0] == spcrng[1] + && (len != destsize + || !si || !is_strlen_related_p (si->ptr, len))) + return; + + location_t loc = gimple_nonartificial_location (stmt); + if (loc == UNKNOWN_LOCATION && dest && EXPR_HAS_LOCATION (dest)) + loc = tree_nonartificial_location (dest); + loc = expansion_point_location_if_in_system_header (loc); + + bool warned = false; + if (wi::leu_p (lenrng[0], spcrng[1])) + { + if (len != destsize + && (!si || !is_strlen_related_p (si->ptr, len))) + return; + + warned = (writefn + ? warning_at (loc, OPT_Wstringop_overflow_, + "%G%qD writing one too many bytes into a region " + "of a size that depends on %<strlen%>", + stmt, writefn) + : warning_at (loc, OPT_Wstringop_overflow_, + "%Gwriting one too many bytes into a region " + "of a size that depends on %<strlen%>", + stmt)); + } + else if (lenrng[0] == lenrng[1]) + { + if (spcrng[0] == spcrng[1]) + warned = (writefn + ? warning_n (loc, OPT_Wstringop_overflow_, + lenrng[0].to_uhwi (), + "%G%qD writing %wu byte into a region " + "of size %wu", + "%G%qD writing %wu bytes into a region " + "of size %wu", + stmt, writefn, lenrng[0].to_uhwi (), + spcrng[0].to_uhwi ()) + : warning_n (loc, OPT_Wstringop_overflow_, + lenrng[0].to_uhwi (), + "%Gwriting %wu byte into a region " + "of size %wu", + "%Gwriting %wu bytes into a region " + "of size %wu", + stmt, lenrng[0].to_uhwi (), + spcrng[0].to_uhwi ())); + else + warned = (writefn + ? warning_n (loc, OPT_Wstringop_overflow_, + lenrng[0].to_uhwi (), + "%G%qD writing %wu byte into a region " + "of size between %wu and %wu", + "%G%qD writing %wu bytes into a region " + "of size between %wu and %wu", + stmt, writefn, lenrng[0].to_uhwi (), + spcrng[0].to_uhwi (), spcrng[1].to_uhwi ()) + : warning_n (loc, OPT_Wstringop_overflow_, + lenrng[0].to_uhwi (), + "%Gwriting %wu byte into a region " + "of size between %wu and %wu", + "%Gwriting %wu bytes into a region " + "of size between %wu and %wu", + stmt, 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_, + "%G%qD writing between %wu and %wu bytes " + "into a region of size %wu", + stmt, writefn, lenrng[0].to_uhwi (), + lenrng[1].to_uhwi (), + spcrng[0].to_uhwi ()) + : warning_at (loc, OPT_Wstringop_overflow_, + "%Gwriting between %wu and %wu bytes " + "into a region of size %wu", + stmt, lenrng[0].to_uhwi (), + lenrng[1].to_uhwi (), + spcrng[0].to_uhwi ())); + else + warned = (writefn + ? warning_at (loc, OPT_Wstringop_overflow_, + "%G%qD writing between %wu and %wu bytes " + "into a region of size between %wu and %wu", + stmt, writefn, lenrng[0].to_uhwi (), + lenrng[1].to_uhwi (), + spcrng[0].to_uhwi (), spcrng[1].to_uhwi ()) + : warning_at (loc, OPT_Wstringop_overflow_, + "%Gwriting between %wu and %wu bytes " + "into a region of size between %wu and %wu", + stmt, lenrng[0].to_uhwi (), + lenrng[1].to_uhwi (), + spcrng[0].to_uhwi (), spcrng[1].to_uhwi ())); + + if (!warned) + return; + + /* If DESTOFF is not null, use it to format the offset value/range. */ + if (destoff) + get_range (destoff, offrng); + + /* Format the offset to keep the number of inform calls from growing + out of control. */ + char offstr[64]; + if (offrng[0] == offrng[1]) + sprintf (offstr, "%lli", (long long) offrng[0].to_shwi ()); + else + sprintf (offstr, "[%lli, %lli]", + (long long) offrng[0].to_shwi (), (long long) offrng[1].to_shwi ()); + + if (destdecl) + { + if (tree size = DECL_SIZE_UNIT (destdecl)) + inform (DECL_SOURCE_LOCATION (destdecl), + "at offset %s to object %qD with size %E declared here", + offstr, destdecl, size); + else + inform (DECL_SOURCE_LOCATION (destdecl), + "at offset %s to object %qD declared here", + offstr, destdecl); + return; + } +} + +/* Convenience wrapper for the above. */ + +static inline void +maybe_warn_overflow (gimple *stmt, unsigned HOST_WIDE_INT len, + const vr_values *rvals = NULL, + strinfo *si = NULL, bool plus_one = false) +{ + maybe_warn_overflow (stmt, build_int_cst (size_type_node, len), rvals, + si, plus_one); +} + /* 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. */ @@ -4333,6 +4701,13 @@ handle_store (gimple_stmt_iterator *gsi, bool *zero_write, const vr_values *rval else if (si == NULL || compare_nonzero_chars (si, offset, rvals) < 0) { *zero_write = initializer_zerop (rhs); + + bool dummy; + unsigned lenrange[] = { UINT_MAX, 0, 0 }; + if (count_nonzero_bytes (rhs, lenrange, &dummy, &dummy, &dummy, + rvals)) + maybe_warn_overflow (stmt, lenrange[2], rvals); + return true; } } @@ -4371,49 +4746,7 @@ handle_store (gimple_stmt_iterator *gsi, bool *zero_write, const vr_values *rval storing_nonzero_p = lenrange[1] > 0; *zero_write = storing_all_zeros_p; - /* Avoid issuing multiple warnings for the same LHS or statement. - For example, -Warray-bounds may have already been issued for - an out-of-bounds subscript. */ - if (!TREE_NO_WARNING (lhs) && !gimple_no_warning_p (stmt)) - { - /* Set to the declaration referenced by LHS (if known). */ - tree decl = NULL_TREE; - if (tree dstsize = compute_objsize (lhs, 1, &decl)) - if (compare_tree_int (dstsize, lenrange[2]) < 0) - { - /* Fall back on the LHS location if the statement - doesn't have one. */ - location_t loc = gimple_nonartificial_location (stmt); - if (loc == UNKNOWN_LOCATION && EXPR_HAS_LOCATION (lhs)) - loc = tree_nonartificial_location (lhs); - loc = expansion_point_location_if_in_system_header (loc); - if (warning_n (loc, OPT_Wstringop_overflow_, - lenrange[2], - "%Gwriting %u byte into a region of size %E", - "%Gwriting %u bytes into a region of size %E", - stmt, lenrange[2], dstsize)) - { - if (decl) - { - if (TREE_CODE (decl) == SSA_NAME) - { - gimple *stmt = SSA_NAME_DEF_STMT (decl); - if (is_gimple_call (stmt)) - { - tree allocfn = gimple_call_fndecl (stmt); - inform (gimple_location (stmt), - "destination region allocated by %qD " - "here", allocfn); - } - } - else - inform (DECL_SOURCE_LOCATION (decl), - "destination object declared here"); - } - gimple_set_no_warning (stmt, true); - } - } - } + maybe_warn_overflow (stmt, lenrange[2], rvals); } else { |