aboutsummaryrefslogtreecommitdiff
path: root/gcc/tree-ssa-strlen.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/tree-ssa-strlen.c')
-rw-r--r--gcc/tree-ssa-strlen.c822
1 files changed, 571 insertions, 251 deletions
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index 212ac71..6ef0741 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -84,14 +84,20 @@ struct strinfo
tree nonzero_chars;
/* Any of the corresponding pointers for querying alias oracle. */
tree ptr;
- /* This is used for two things:
+ /* 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 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;
@@ -189,20 +195,21 @@ 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. */
+ and returns either the constant value or VAL on success or null
+ when the range couldn't be determined. Uses RVALS when nonnull
+ to determine the range, otherwise get_range_info. */
-static bool
-get_range (tree val, wide_int minmax[2], const vr_values *rvals = NULL)
+tree
+get_range (tree val, wide_int minmax[2], const vr_values *rvals /* = NULL */)
{
- if (tree_fits_uhwi_p (val))
+ if (TREE_CODE (val) == INTEGER_CST)
{
minmax[0] = minmax[1] = wi::to_wide (val);
- return true;
+ return val;
}
if (TREE_CODE (val) != SSA_NAME)
- return false;
+ return NULL_TREE;
if (rvals)
{
@@ -215,20 +222,20 @@ get_range (tree val, wide_int minmax[2], const vr_values *rvals = NULL)
= (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;
+ return NULL_TREE;
minmax[0] = wi::to_wide (vr->min ());
minmax[1] = wi::to_wide (vr->max ());
- return true;
+ return val;
}
value_range_kind rng = get_range_info (val, minmax, minmax + 1);
if (rng == VR_RANGE)
- return true;
+ return val;
/* 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 NULL_TREE;
}
/* Return:
@@ -320,7 +327,7 @@ get_next_strinfo (strinfo *si)
/* 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. */
+ *OFFSET_OUT. When nonnull uses RVALS to determine range information. */
static int
get_addr_stridx (tree exp, tree ptr, unsigned HOST_WIDE_INT *offset_out,
@@ -380,13 +387,14 @@ get_addr_stridx (tree exp, tree ptr, unsigned HOST_WIDE_INT *offset_out,
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. */
+ 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)
+get_stridx (tree exp, wide_int offrng[2] = NULL, const vr_values *rvals = NULL)
{
if (offrng)
- offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (sizetype));
+ offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (ptrdiff_type_node));
if (TREE_CODE (exp) == SSA_NAME)
{
@@ -465,7 +473,7 @@ get_stridx (tree exp, wide_int offrng[2] = NULL)
return the index corresponding to the SSA_NAME.
Do this irrespective of the whether the offset
is known. */
- if (get_range (off, offrng))
+ if (get_range (off, offrng, rvals))
{
/* When the offset range is known, increment it
it by the constant offset computed in prior
@@ -672,6 +680,7 @@ new_strinfo (tree ptr, int idx, tree nonzero_chars, bool full_string_p)
si->nonzero_chars = nonzero_chars;
si->ptr = ptr;
si->stmt = NULL;
+ si->alloc = NULL;
si->endptr = NULL_TREE;
si->refcount = 1;
si->idx = idx;
@@ -838,6 +847,8 @@ get_string_length (strinfo *si)
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. */
@@ -885,45 +896,57 @@ dump_strlen_info (FILE *fp, gimple *stmt, const vr_values *rvals)
fprintf (fp, ", ptr = ");
print_generic_expr (fp, si->ptr);
}
- fprintf (fp, ", nonzero_chars = ");
- print_generic_expr (fp, si->nonzero_chars);
- if (TREE_CODE (si->nonzero_chars) == SSA_NAME)
+
+ if (si->nonzero_chars)
{
- value_range_kind rng = VR_UNDEFINED;
- wide_int min, max;
- if (rvals)
+ fprintf (fp, ", nonzero_chars = ");
+ print_generic_expr (fp, si->nonzero_chars);
+ if (TREE_CODE (si->nonzero_chars) == SSA_NAME)
{
- const value_range_equiv *vr
- = CONST_CAST (class vr_values *, rvals)
- ->get_value_range (si->nonzero_chars);
- rng = vr->kind ();
- if (range_int_cst_p (vr))
+ value_range_kind rng = VR_UNDEFINED;
+ wide_int min, max;
+ if (rvals)
{
- min = wi::to_wide (vr->min ());
- max = wi::to_wide (vr->max ());
+ const value_range *vr
+ = CONST_CAST (class vr_values *, rvals)
+ ->get_value_range (si->nonzero_chars);
+ 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
- rng = VR_UNDEFINED;
- }
- else
- rng = get_range_info (si->nonzero_chars, &min, &max);
+ rng = get_range_info (si->nonzero_chars, &min, &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 ());
+ 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);
+
+ 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))
@@ -1197,80 +1220,87 @@ get_range_strlen_dynamic (tree src, c_strlen_data *pdata,
BITMAP_FREE (visited);
}
-/* Invalidate string length information for strings whose length
- might change due to stores in stmt, except those marked DON'T
- INVALIDATE. For string-modifying statements, ZERO_WRITE is
- set when the statement wrote only zeros. */
+/* 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()\n", __func__);
+ {
+ fprintf (dump_file, "%s called for ", __func__);
+ print_gimple_stmt (dump_file, stmt, TDF_LINENO);
+ }
strinfo *si;
- unsigned int i;
bool nonempty = false;
- for (i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i)
- if (si != NULL)
- {
- if (!si->dont_invalidate)
- {
- ao_ref r;
- tree size = NULL_TREE;
- if (si->nonzero_chars)
- {
- /* Include the terminating nul in the size of the string
- to consider when determining possible clobber. */
- tree type = TREE_TYPE (si->nonzero_chars);
- size = fold_build2 (PLUS_EXPR, type, si->nonzero_chars,
- build_int_cst (type, 1));
- }
- ao_ref_init_from_ptr_and_size (&r, si->ptr, size);
- if (stmt_may_clobber_ref_p_1 (stmt, &r))
- {
- if (dump_file && (dump_flags & TDF_DETAILS))
- {
- if (size && tree_fits_uhwi_p (size))
- fprintf (dump_file,
- " statement may clobber string "
- HOST_WIDE_INT_PRINT_UNSIGNED " long\n",
- tree_to_uhwi (size));
- else
- fprintf (dump_file,
- " statement may clobber string\n");
- }
+ for (unsigned i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i)
+ {
+ if (si == NULL || !POINTER_TYPE_P (TREE_TYPE (si->ptr)))
+ continue;
- set_strinfo (i, NULL);
- free_strinfo (si);
- continue;
- }
+ nonempty = true;
- 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 allocation
- 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;
- }
- }
- si->dont_invalidate = false;
- 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 = NULL_TREE;
+ if (si->nonzero_chars)
+ {
+ /* Include the terminating nul in the size of the string
+ to consider when determining possible clobber. */
+ tree type = TREE_TYPE (si->nonzero_chars);
+ size = fold_build2 (PLUS_EXPR, type, si->nonzero_chars,
+ build_int_cst (type, 1));
+ }
+ ao_ref_init_from_ptr_and_size (&r, si->ptr, size);
+ 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() ==> %i\n", __func__, nonempty);
+ fprintf (dump_file, "%s returns %i\n", __func__, nonempty);
return nonempty;
}
@@ -1289,6 +1319,7 @@ unshare_strinfo (strinfo *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;
@@ -1582,6 +1613,8 @@ valid_builtin_call (gimple *stmt)
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:
@@ -1858,92 +1891,159 @@ maybe_set_strlen_range (tree lhs, tree src, tree bound)
}
/* Diagnose buffer overflow by a STMT writing LEN + PLUS_ONE bytes,
- into an object designated by the LHS of STMT otherise. */
+ either into a region allocated for the object SI when non-null,
+ or into an object designated by the LHS of STMT otherwise.
+ 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. */
static void
maybe_warn_overflow (gimple *stmt, tree len,
const vr_values *rvals = NULL,
- strinfo *si = NULL, bool plus_one = false)
+ strinfo *si = NULL, bool plus_one = false,
+ bool rawmem = false)
{
if (!len || gimple_no_warning_p (stmt))
return;
+ /* The DECL of the function performing the write if it is done
+ by one. */
tree writefn = NULL_TREE;
- tree destdecl = NULL_TREE;
- tree destsize = NULL_TREE;
+ /* The destination expression involved in the store STMT. */
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;
- }
+ dest = gimple_assign_lhs (stmt);
else if (is_gimple_call (stmt))
{
- writefn = gimple_call_fndecl (stmt);
dest = gimple_call_arg (stmt, 0);
+ writefn = gimple_call_fndecl (stmt);
}
+ if (TREE_NO_WARNING (dest))
+ return;
+
/* The offset into the destination object computed below and not
- reflected in DESTSIZE. Either DESTOFF is set above or OFFRNG
- below. */
+ reflected in DESTSIZE. */
wide_int offrng[2];
- offrng[0] = wi::zero (TYPE_PRECISION (sizetype));
- offrng[1] = offrng[0];
+ const int off_prec = TYPE_PRECISION (ptrdiff_type_node);
+ offrng[0] = offrng[1] = wi::zero (off_prec);
- if (!destsize && !si && dest)
+ if (!si)
{
- /* For both assignments and calls, if no destination STRINFO was
- provided, try to get it from the DEST. */
+ /* If no destination STRINFO was provided try to get it from
+ the DEST argument. */
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);
+ tree off = TREE_OPERAND (ref, 1);
ref = TREE_OPERAND (ref, 0);
+ if (get_range (off, offrng, rvals))
+ {
+ offrng[0] = offrng[0].from (offrng[0], off_prec, SIGNED);
+ offrng[1] = offrng[1].from (offrng[1], off_prec, SIGNED);
+ }
+ else
+ {
+ offrng[0] = wi::to_wide (TYPE_MIN_VALUE (ptrdiff_type_node));
+ offrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node));
+ }
}
if (TREE_CODE (ref) == MEM_REF)
{
tree mem_off = TREE_OPERAND (ref, 1);
- if (off)
+ ref = TREE_OPERAND (ref, 0);
+ wide_int memoffrng[2];
+ if (get_range (mem_off, memoffrng, rvals))
{
- if (!integer_zerop (mem_off))
- return;
+ offrng[0] += memoffrng[0];
+ offrng[1] += memoffrng[1];
}
else
- off = mem_off;
- ref = TREE_OPERAND (ref, 0);
+ {
+ offrng[0] = wi::to_wide (TYPE_MIN_VALUE (ptrdiff_type_node));
+ offrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node));
+ }
}
- if (int idx = get_stridx (ref, offrng))
+ wide_int stroffrng[2];
+ if (int idx = get_stridx (ref, stroffrng, rvals))
{
si = get_strinfo (idx);
- if (off && TREE_CODE (off) == INTEGER_CST)
+ offrng[0] += stroffrng[0];
+ offrng[1] += stroffrng[1];
+ }
+ }
+
+ /* The allocation call if the destination object was allocated
+ by one. */
+ gimple *alloc_call = NULL;
+ /* The DECL of the destination object if known and not dynamically
+ allocated. */
+ tree destdecl = NULL_TREE;
+ /* The offset into the destination object set by compute_objsize
+ but already reflected in DESTSIZE. */
+ tree destoff = NULL_TREE;
+ /* The size of the destination region (which is smaller than
+ the destination object for stores at a non-zero offset). */
+ tree destsize = NULL_TREE;
+
+ /* Compute the range of sizes of the destination object. The range
+ is constant for declared objects but may be a range for allocated
+ objects. */
+ const int siz_prec = TYPE_PRECISION (size_type_node);
+ wide_int sizrng[2];
+ if (si)
+ {
+ destsize = gimple_call_alloc_size (si->alloc, sizrng, rvals);
+ alloc_call = si->alloc;
+ }
+ else
+ offrng[0] = offrng[1] = wi::zero (off_prec);
+
+ if (!destsize)
+ {
+ /* If there is no STRINFO for DEST, fall back on compute_objsize. */
+ tree off = NULL_TREE;
+ destsize = compute_objsize (dest, rawmem ? 0 : 1, &destdecl, &off, rvals);
+ if (destsize)
+ {
+ /* Remember OFF but clear OFFRNG that may have been set above. */
+ destoff = off;
+ offrng[0] = offrng[1] = wi::zero (off_prec);
+
+ if (destdecl && TREE_CODE (destdecl) == SSA_NAME)
{
- wide_int wioff = wi::to_wide (off, offrng->get_precision ());
- offrng[0] += wioff;
- offrng[1] += wioff;
+ gimple *stmt = SSA_NAME_DEF_STMT (destdecl);
+ if (is_gimple_call (stmt))
+ alloc_call = stmt;
+ destdecl = NULL_TREE;
+ }
+
+ if (!get_range (destsize, sizrng, rvals))
+ {
+ /* On failure, rather than failing, set 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 (siz_prec);
+ sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (sizetype));
}
}
- else
- return;
}
+ if (!destsize)
+ {
+ sizrng[0] = wi::zero (siz_prec);
+ sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (sizetype));
+ };
+
+ sizrng[0] = sizrng[0].from (sizrng[0], siz_prec, UNSIGNED);
+ sizrng[1] = sizrng[1].from (sizrng[1], siz_prec, UNSIGNED);
+
/* 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
@@ -1961,37 +2061,43 @@ maybe_warn_overflow (gimple *stmt, tree len,
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. */
+ /* 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]))
+ if (wi::neg_p (offrng[0]) && wi::neg_p (offrng[1]))
{
- /* FIXME: Handle negative offsets into allocated objects. */
- if (destdecl)
- spcrng[0] = spcrng[1] = wi::zero (spcrng->get_precision ());
- else
+ /* When the offset is negative and the size of the destination
+ object unknown there is little to do.
+ FIXME: Detect offsets that are necessarily invalid regardless
+ of the size of the object. */
+ if (!destsize)
return;
+
+ /* The remaining space is necessarily zero. */
+ spcrng[0] = spcrng[1] = wi::zero (spcrng->get_precision ());
+ }
+ else if (wi::neg_p (offrng[0]))
+ {
+ /* When the lower bound of the offset is negative but the upper
+ bound is not, reduce the upper bound of the remaining space
+ by the upper bound of the offset but leave the lower bound
+ unchanged. If that makes the upper bound of the space less
+ than the lower bound swap the two. */
+ spcrng[1] -= wi::ltu_p (offrng[1], spcrng[1]) ? offrng[1] : spcrng[1];
+ if (wi::ltu_p (spcrng[1], spcrng[0]))
+ std::swap (spcrng[1], spcrng[0]);
}
else
{
+ /* When the offset is positive reduce the remaining space by
+ the lower bound of the offset or clear it if the offset is
+ greater. */
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]))
+ if (wi::leu_p (lenrng[0], spcrng[0])
+ && wi::leu_p (lenrng[1], spcrng[1]))
return;
if (lenrng[0] == spcrng[1]
@@ -2092,6 +2198,8 @@ maybe_warn_overflow (gimple *stmt, tree len,
if (!warned)
return;
+ gimple_set_no_warning (stmt, true);
+
/* If DESTOFF is not null, use it to format the offset value/range. */
if (destoff)
get_range (destoff, offrng);
@@ -2117,17 +2225,91 @@ maybe_warn_overflow (gimple *stmt, tree len,
offstr, destdecl);
return;
}
+
+ if (!alloc_call)
+ return;
+
+ tree allocfn = gimple_call_fndecl (alloc_call);
+ if (!allocfn)
+ {
+ /* For an ALLOC_CALL via a function pointer make a small effort
+ to determine the destination of the pointer. */
+ allocfn = gimple_call_fn (alloc_call);
+ if (TREE_CODE (allocfn) == SSA_NAME)
+ {
+ gimple *def = SSA_NAME_DEF_STMT (allocfn);
+ if (gimple_assign_single_p (def))
+ {
+ tree rhs = gimple_assign_rhs1 (def);
+ if (DECL_P (rhs))
+ allocfn = rhs;
+ else if (TREE_CODE (rhs) == COMPONENT_REF)
+ allocfn = TREE_OPERAND (rhs, 1);
+ }
+ }
+ }
+
+ if (gimple_call_builtin_p (alloc_call, BUILT_IN_ALLOCA_WITH_ALIGN))
+ {
+ if (sizrng[0] == sizrng[1])
+ inform (gimple_location (alloc_call),
+ "at offset %s to an object with size %wu declared here",
+ offstr, sizrng[0].to_uhwi ());
+ else if (sizrng[0] == 0)
+ {
+ /* Avoid printing impossible sizes. */
+ if (wi::ltu_p (sizrng[1],
+ wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2))
+ inform (gimple_location (alloc_call),
+ "at offset %s to an object with size at most %wu "
+ "declared here",
+ offstr, sizrng[1].to_uhwi ());
+ else
+ inform (gimple_location (alloc_call),
+ "at offset %s to an object declared here", offstr);
+ }
+ else
+ inform (gimple_location (alloc_call),
+ "at offset %s to an object with size between %wu and %wu "
+ "declared here",
+ offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi ());
+ return;
+ }
+
+ if (sizrng[0] == sizrng[1])
+ inform (gimple_location (alloc_call),
+ "at offset %s to an object with size %wu allocated by %qE here",
+ offstr, sizrng[0].to_uhwi (), allocfn);
+ else if (sizrng[0] == 0)
+ {
+ /* Avoid printing impossible sizes. */
+ if (wi::ltu_p (sizrng[1],
+ wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2))
+ inform (gimple_location (alloc_call),
+ "at offset %s to an object with size at most %wu allocated "
+ "by %qD here",
+ offstr, sizrng[1].to_uhwi (), allocfn);
+ else
+ inform (gimple_location (alloc_call),
+ "at offset %s to an object allocated by %qE here",
+ offstr, allocfn);
+ }
+ else
+ inform (gimple_location (alloc_call),
+ "at offset %s to an object with size between %wu and %wu "
+ "allocated by %qE here",
+ offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi (), allocfn);
}
/* 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)
+ const vr_values *rvals = NULL, strinfo *si = NULL,
+ bool plus_one = false, bool rawmem = false)
{
maybe_warn_overflow (stmt, build_int_cst (size_type_node, len), rvals,
- si, plus_one);
+ si, plus_one, rawmem);
}
/* Handle a strlen call. If strlen of the argument is known, replace
@@ -2243,7 +2425,7 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
tree old = si->nonzero_chars;
si->nonzero_chars = lhs;
si->full_string_p = true;
- if (TREE_CODE (old) == INTEGER_CST)
+ if (old && TREE_CODE (old) == INTEGER_CST)
{
old = fold_convert_loc (loc, TREE_TYPE (lhs), old);
tree adj = fold_build2_loc (loc, MINUS_EXPR,
@@ -2422,10 +2604,11 @@ handle_builtin_strchr (gimple_stmt_iterator *gsi)
/* 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. */
+ memcpy. Uses RVALS to determine range information. */
static void
-handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
+handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
+ const vr_values *rvals)
{
int idx, didx;
tree src, dst, srclen, len, lhs, type, fn, oldlen;
@@ -2459,6 +2642,11 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
else if (idx < 0)
srclen = build_int_cst (size_type_node, ~idx);
+ maybe_warn_overflow (stmt, srclen, rvals, olddsi, true);
+
+ if (olddsi != NULL)
+ adjust_last_stmt (olddsi, stmt, false);
+
loc = gimple_location (stmt);
if (srclen == NULL_TREE)
switch (bcode)
@@ -2709,26 +2897,58 @@ is_strlen_related_p (tree src, tree len)
if (TREE_CODE (len) != SSA_NAME)
return false;
- gimple *def_stmt = SSA_NAME_DEF_STMT (len);
- if (!def_stmt)
+ 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 (def_stmt))
+ if (is_gimple_call (lendef))
{
- tree func = gimple_call_fndecl (def_stmt);
- if (!valid_builtin_call (def_stmt)
+ 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 (def_stmt, 0);
+ tree arg = gimple_call_arg (lendef, 0);
return is_strlen_related_p (src, arg);
}
- if (!is_gimple_assign (def_stmt))
+ if (!is_gimple_assign (lendef))
return false;
- tree_code code = gimple_assign_rhs_code (def_stmt);
- tree rhs1 = gimple_assign_rhs1 (def_stmt);
+ 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)
@@ -2741,7 +2961,7 @@ is_strlen_related_p (tree src, tree len)
return is_strlen_related_p (src, rhs1);
}
- if (tree rhs2 = gimple_assign_rhs2 (def_stmt))
+ if (tree rhs2 = gimple_assign_rhs2 (lendef))
{
/* Integer subtraction is considered strlen-related when both
arguments are integers and second one is strlen-related. */
@@ -3187,34 +3407,37 @@ handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
/* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call.
If strlen of the second argument is known and length of the third argument
is that plus one, strlen of the first argument is the same after this
- call. */
+ call. Uses RVALS to determine range information. */
static void
-handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
+handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
+ const vr_values *rvals)
{
- int idx, didx;
- tree src, dst, len, lhs, oldlen, newlen;
+ tree lhs, oldlen, newlen;
gimple *stmt = gsi_stmt (*gsi);
- strinfo *si, *dsi, *olddsi;
+ strinfo *si, *dsi;
- len = gimple_call_arg (stmt, 2);
- src = gimple_call_arg (stmt, 1);
- dst = gimple_call_arg (stmt, 0);
- idx = get_stridx (src);
- if (idx == 0)
- return;
+ tree len = gimple_call_arg (stmt, 2);
+ tree src = gimple_call_arg (stmt, 1);
+ tree dst = gimple_call_arg (stmt, 0);
- didx = get_stridx (dst);
- olddsi = NULL;
+ int didx = get_stridx (dst);
+ strinfo *olddsi = NULL;
if (didx > 0)
olddsi = get_strinfo (didx);
else if (didx < 0)
return;
if (olddsi != NULL
- && tree_fits_uhwi_p (len)
&& !integer_zerop (len))
- adjust_last_stmt (olddsi, stmt, false);
+ {
+ maybe_warn_overflow (stmt, len, rvals, olddsi, false, true);
+ adjust_last_stmt (olddsi, stmt, false);
+ }
+
+ int idx = get_stridx (src);
+ if (idx == 0)
+ return;
bool full_string_p;
if (idx > 0)
@@ -3611,10 +3834,11 @@ handle_builtin_strcat (enum built_in_function bcode, gimple_stmt_iterator *gsi)
gimple_set_no_warning (stmt, true);
}
-/* Handle a call to malloc or calloc. */
+/* Handle a call to an allocation function like alloca, malloc or calloc,
+ or an ordinary allocation function declared with attribute alloc_size. */
static void
-handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi)
+handle_alloc_call (enum built_in_function bcode, gimple_stmt_iterator *gsi)
{
gimple *stmt = gsi_stmt (*gsi);
tree lhs = gimple_call_lhs (stmt);
@@ -3628,59 +3852,89 @@ handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi)
length = build_int_cst (size_type_node, 0);
strinfo *si = new_strinfo (lhs, idx, length, length != NULL_TREE);
if (bcode == BUILT_IN_CALLOC)
- si->endptr = lhs;
+ {
+ /* Only set STMT for calloc and malloc. */
+ si->stmt = stmt;
+ /* Only set ENDPTR for calloc. */
+ si->endptr = lhs;
+ }
+ else if (bcode == BUILT_IN_MALLOC)
+ si->stmt = stmt;
+
+ /* Set ALLOC is set for all allocation functions. */
+ si->alloc = stmt;
set_strinfo (idx, si);
si->writable = true;
- si->stmt = stmt;
si->dont_invalidate = true;
}
/* Handle a call to memset.
After a call to calloc, memset(,0,) is unnecessary.
memset(malloc(n),0,n) is calloc(n,1).
- return true when the call is transformed, false otherwise. */
+ return true when the call is transformed, false otherwise.
+ When nonnull uses RVALS to determine range information. */
static bool
-handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write)
+handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write,
+ const vr_values *rvals)
{
- gimple *stmt2 = gsi_stmt (*gsi);
- if (!integer_zerop (gimple_call_arg (stmt2, 1)))
- return false;
-
- /* Let the caller know the memset call cleared the destination. */
- *zero_write = true;
-
- tree ptr = gimple_call_arg (stmt2, 0);
- int idx1 = get_stridx (ptr);
+ gimple *memset_stmt = gsi_stmt (*gsi);
+ tree ptr = gimple_call_arg (memset_stmt, 0);
+ /* Set to the non-constant offset added to PTR. */
+ wide_int offrng[2];
+ int idx1 = get_stridx (ptr, offrng, rvals);
if (idx1 <= 0)
return false;
strinfo *si1 = get_strinfo (idx1);
if (!si1)
return false;
- gimple *stmt1 = si1->stmt;
- if (!stmt1 || !is_gimple_call (stmt1))
+ gimple *alloc_stmt = si1->alloc;
+ if (!alloc_stmt || !is_gimple_call (alloc_stmt))
+ return false;
+ tree callee1 = gimple_call_fndecl (alloc_stmt);
+ if (!valid_builtin_call (alloc_stmt))
+ return false;
+ tree alloc_size = gimple_call_arg (alloc_stmt, 0);
+ tree memset_size = gimple_call_arg (memset_stmt, 2);
+
+ /* Check for overflow. */
+ maybe_warn_overflow (memset_stmt, memset_size, rvals, NULL, false, true);
+
+ /* Bail when there is no statement associated with the destination
+ (the statement may be null even when SI1->ALLOC is not). */
+ if (!si1->stmt)
return false;
- tree callee1 = gimple_call_fndecl (stmt1);
- if (!valid_builtin_call (stmt1))
+
+ /* Avoid optimizing if store is at a variable offset from the beginning
+ of the allocated object. */
+ if (offrng[0] != 0 || offrng[0] != offrng[1])
return false;
+
+ /* Bail when the call writes a non-zero value. */
+ if (!integer_zerop (gimple_call_arg (memset_stmt, 1)))
+ return false;
+
+ /* Let the caller know the memset call cleared the destination. */
+ *zero_write = true;
+
enum built_in_function code1 = DECL_FUNCTION_CODE (callee1);
- tree size = gimple_call_arg (stmt2, 2);
if (code1 == BUILT_IN_CALLOC)
- /* Not touching stmt1 */ ;
+ /* Not touching alloc_stmt */ ;
else if (code1 == BUILT_IN_MALLOC
- && operand_equal_p (gimple_call_arg (stmt1, 0), size, 0))
+ && operand_equal_p (memset_size, alloc_size, 0))
{
- gimple_stmt_iterator gsi1 = gsi_for_stmt (stmt1);
+ /* Replace the malloc + memset calls with calloc. */
+ gimple_stmt_iterator gsi1 = gsi_for_stmt (si1->stmt);
update_gimple_call (&gsi1, builtin_decl_implicit (BUILT_IN_CALLOC), 2,
- size, build_one_cst (size_type_node));
+ alloc_size, build_one_cst (size_type_node));
si1->nonzero_chars = build_int_cst (size_type_node, 0);
si1->full_string_p = true;
si1->stmt = gsi_stmt (gsi1);
}
else
return false;
- tree lhs = gimple_call_lhs (stmt2);
- unlink_stmt_vdef (stmt2);
+ tree lhs = gimple_call_lhs (memset_stmt);
+ unlink_stmt_vdef (memset_stmt);
if (lhs)
{
gimple *assign = gimple_build_assign (lhs, ptr);
@@ -3689,7 +3943,7 @@ handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write)
else
{
gsi_remove (gsi, true);
- release_defs (stmt2);
+ release_defs (memset_stmt);
}
return true;
@@ -4391,7 +4645,8 @@ int ssa_name_limit_t::next_ssa_name (tree ssa_name)
OFFSET and NBYTES are the offset into the representation and
the size of the access to it determined from a MEM_REF or zero
for other expressions.
- Avoid recursing deeper than the limits in SNLIM allow.
+ Uses RVALS to determine range information.
+ Avoids recursing deeper than the limits in SNLIM allow.
Returns true on success and false otherwise. */
static bool
@@ -4438,6 +4693,29 @@ count_nonzero_bytes (tree exp, unsigned HOST_WIDE_INT offset,
if (maxlen + 1 < nbytes)
return false;
+ if (!nbytes
+ && TREE_CODE (si->ptr) == SSA_NAME
+ && !POINTER_TYPE_P (TREE_TYPE (si->ptr)))
+ {
+ /* SI->PTR is an SSA_NAME with a DEF_STMT like
+ _1 = MEM <unsigned int> [(char * {ref-all})s_4(D)]; */
+ gimple *stmt = SSA_NAME_DEF_STMT (exp);
+ if (gimple_assign_single_p (stmt)
+ && gimple_assign_rhs_code (stmt) == MEM_REF)
+ {
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (tree refsize = TYPE_SIZE_UNIT (TREE_TYPE (rhs)))
+ if (tree_fits_uhwi_p (refsize))
+ {
+ nbytes = tree_to_uhwi (refsize);
+ maxlen = nbytes;
+ }
+ }
+
+ if (!nbytes)
+ return false;
+ }
+
if (nbytes <= minlen)
*nulterm = false;
@@ -4454,7 +4732,7 @@ count_nonzero_bytes (tree exp, unsigned HOST_WIDE_INT offset,
lenrange[1] = maxlen;
if (lenrange[2] < nbytes)
- (lenrange[2] = nbytes);
+ lenrange[2] = nbytes;
/* Since only the length of the string are known and not its contents,
clear ALLNUL and ALLNONNUL purely on the basis of the length. */
@@ -4672,7 +4950,8 @@ count_nonzero_bytes (tree exp, unsigned lenrange[3], bool *nulterm,
the next statement in the basic block and false otherwise. */
static bool
-handle_store (gimple_stmt_iterator *gsi, bool *zero_write, const vr_values *rvals)
+handle_store (gimple_stmt_iterator *gsi, bool *zero_write,
+ const vr_values *rvals)
{
int idx = -1;
strinfo *si = NULL;
@@ -5076,16 +5355,23 @@ is_char_type (tree type)
}
/* Check the built-in call at GSI for validity and optimize it.
+ Uses RVALS to determine range information.
Return true to let the caller advance *GSI to the next statement
in the basic block and false otherwise. */
static bool
-strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
- bool *zero_write,
+strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, bool *zero_write,
const vr_values *rvals)
{
gimple *stmt = gsi_stmt (*gsi);
+ if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
+ {
+ tree fntype = gimple_call_fntype (stmt);
+ if (fntype && lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (fntype)))
+ handle_alloc_call (BUILT_IN_NONE, gsi);
+ }
+
/* When not optimizing we must be checking printf calls which
we do even for user-defined functions when they are declared
with attribute format. */
@@ -5108,7 +5394,7 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
case BUILT_IN_STRCPY_CHK:
case BUILT_IN_STPCPY:
case BUILT_IN_STPCPY_CHK:
- handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
+ handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi, rvals);
break;
case BUILT_IN_STRNCAT:
@@ -5127,18 +5413,20 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
case BUILT_IN_MEMCPY_CHK:
case BUILT_IN_MEMPCPY:
case BUILT_IN_MEMPCPY_CHK:
- handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi);
+ handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi, rvals);
break;
case BUILT_IN_STRCAT:
case BUILT_IN_STRCAT_CHK:
handle_builtin_strcat (DECL_FUNCTION_CODE (callee), gsi);
break;
+ case BUILT_IN_ALLOCA:
+ case BUILT_IN_ALLOCA_WITH_ALIGN:
case BUILT_IN_MALLOC:
case BUILT_IN_CALLOC:
- handle_builtin_malloc (DECL_FUNCTION_CODE (callee), gsi);
+ handle_alloc_call (DECL_FUNCTION_CODE (callee), gsi);
break;
case BUILT_IN_MEMSET:
- if (handle_builtin_memset (gsi, zero_write))
+ if (handle_builtin_memset (gsi, zero_write, rvals))
return false;
break;
case BUILT_IN_MEMCMP:
@@ -5163,7 +5451,8 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
If GSI's basic block needs clean-up of EH, set *CLEANUP_EH to true. */
static void
-handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh)
+handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh,
+ const vr_values *rvals)
{
gimple *stmt = gsi_stmt (*gsi);
tree lhs = gimple_assign_lhs (stmt);
@@ -5266,6 +5555,31 @@ handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh)
}
}
}
+ else if (code == MEM_REF && TREE_CODE (lhs) == SSA_NAME)
+ {
+ if (int idx = new_stridx (lhs))
+ {
+ /* Record multi-byte assignments from MEM_REFs. */
+ bool storing_all_nonzero_p;
+ bool storing_all_zeros_p;
+ bool full_string_p;
+ unsigned lenrange[] = { UINT_MAX, 0, 0 };
+ tree rhs = gimple_assign_rhs1 (stmt);
+ const bool ranges_valid
+ = count_nonzero_bytes (rhs, lenrange, &full_string_p,
+ &storing_all_zeros_p, &storing_all_nonzero_p,
+ rvals);
+ if (ranges_valid)
+ {
+ tree length = build_int_cst (sizetype, lenrange[0]);
+ strinfo *si = new_strinfo (lhs, idx, length, full_string_p);
+ set_strinfo (idx, si);
+ si->writable = true;
+ si->dont_invalidate = true;
+ maybe_warn_overflow (stmt, lenrange[2], rvals);
+ }
+ }
+ }
if (strlen_to_stridx)
{
@@ -5318,29 +5632,35 @@ check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh,
}
else if (TREE_CODE (lhs) == SSA_NAME && INTEGRAL_TYPE_P (lhs_type))
/* Handle assignment to a character. */
- handle_integral_assign (gsi, cleanup_eh);
+ handle_integral_assign (gsi, cleanup_eh, rvals);
else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
{
tree type = TREE_TYPE (lhs);
if (TREE_CODE (type) == ARRAY_TYPE)
type = TREE_TYPE (type);
- bool is_char_store = is_char_type (type);
- if (!is_char_store && TREE_CODE (lhs) == MEM_REF)
- {
- /* To consider stores into char objects via integer types
- other than char but not those to non-character objects,
- determine the type of the destination rather than just
- the type of the access. */
- tree ref = TREE_OPERAND (lhs, 0);
- type = TREE_TYPE (ref);
- if (TREE_CODE (type) == POINTER_TYPE)
- type = TREE_TYPE (type);
- if (TREE_CODE (type) == ARRAY_TYPE)
- type = TREE_TYPE (type);
- if (is_char_type (type))
- is_char_store = true;
- }
+ bool is_char_store = is_char_type (type);
+ if (!is_char_store && TREE_CODE (lhs) == MEM_REF)
+ {
+ /* To consider stores into char objects via integer types
+ other than char but not those to non-character objects,
+ determine the type of the destination rather than just
+ the type of the access. */
+ for (int i = 0; i != 2; ++i)
+ {
+ tree ref = TREE_OPERAND (lhs, i);
+ type = TREE_TYPE (ref);
+ if (TREE_CODE (type) == POINTER_TYPE)
+ type = TREE_TYPE (type);
+ if (TREE_CODE (type) == ARRAY_TYPE)
+ type = TREE_TYPE (type);
+ if (is_char_type (type))
+ {
+ is_char_store = true;
+ break;
+ }
+ }
+ }
/* Handle a single or multibyte assignment. */
if (is_char_store && !handle_store (gsi, &zero_write, rvals))