PR middle-end/91582 - missing heap overflow detection for strcpy
gcc/ChangeLog:
PR middle-end/91582
* builtins.c (gimple_call_alloc_size): Add argument.
* builtins.h (gimple_call_alloc_size): Same.
* tree-ssa-strlen.c (strinfo::alloc): New member.
(get_addr_stridx): Add argument.
(get_stridx): Use ptrdiff_t. Add argument.
(new_strinfo): Set new member.
(get_string_length): Handle alloca and VLA.
(dump_strlen_info): Dump more state.
(maybe_invalidate): Print more info. Decrease indentation.
(unshare_strinfo): Set new member.
(valid_builtin_call): Handle alloca and VLA.
(maybe_warn_overflow): Check and set no-warning bit. Improve
handling of offsets. Print allocated objects.
(handle_builtin_strlen): Handle strinfo records with null lengths.
(handle_builtin_strcpy): Add argument. Call maybe_warn_overflow.
(is_strlen_related_p): Handle dynamically allocated objects.
(get_range): Add argument.
(handle_builtin_malloc): Rename...
(handle_aalloc): ...to this and handle all allocation functions.
(handle_builtin_memset): Call maybe_warn_overflow.
(count_nonzero_bytes): Handle more MEM_REF forms.
(strlen_check_and_optimize_call): Call handle_alloc_call. Pass
arguments to more callees.
(handle_integral_assign): Add argument. Create strinfo entries
for MEM_REF assignments.
(check_and_optimize_stmt): Handle more MEM_REF forms.
gcc/testsuite/ChangeLog:
PR middle-end/91582
* c-c++-common/Wrestrict.c: Adjust expected warnings.
* gcc.dg/Warray-bounds-46.c: Disable -Wstringop-overflow.
* gcc.dg/Warray-bounds-47.c: Same.
* gcc.dg/Warray-bounds-52.c: New test.
* gcc.dg/Wstringop-overflow-26.c: New test.
* gcc.dg/Wstringop-overflow-27.c: New test.
* gcc.dg/Wstringop-overflow-28.c: New test.
* gcc.dg/attr-alloc_size.c (test): Disable -Warray-bounds.
* gcc.dg/attr-copy-2.c: Adjust expected warnings.
* gcc.dg/builtin-stringop-chk-5.c: Adjust text of expected messages.
* gcc.dg/strlenopt-86.c: Relax test.
* gcc.target/i386/pr82002-1.c: Prune expected warnings.
@@ -48,6 +48,7 @@ along with GCC; see the file COPYING3. If not see
#include "calls.h"
#include "varasm.h"
#include "tree-object-size.h"
+#include "tree-ssa-strlen.h"
#include "realmpfr.h"
#include "cfgrtl.h"
#include "except.h"
@@ -3697,10 +3698,12 @@ check_access (tree exp, tree, tree, tree dstwrite,
}
/* If STMT is a call to an allocation function, returns the size
- of the object allocated by the call. */
+ of the object allocated by the call. If nonnull, set RNG1[]
+ to the range of the size. */
tree
-gimple_call_alloc_size (gimple *stmt)
+gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
+ const vr_values *rvals /* = NULL */)
{
if (!stmt)
return NULL_TREE;
@@ -3747,11 +3750,12 @@ gimple_call_alloc_size (gimple *stmt)
tree size = gimple_call_arg (stmt, argidx1);
- wide_int rng1[2];
- if (TREE_CODE (size) == INTEGER_CST)
- rng1[0] = rng1[1] = wi::to_wide (size);
- else if (TREE_CODE (size) != SSA_NAME
- || get_range_info (size, rng1, rng1 + 1) != VR_RANGE)
+ wide_int rng1_buf[2];
+ /* If RNG1 is not set, use the buffer. */
+ if (!rng1)
+ rng1 = rng1_buf;
+
+ if (!get_range (size, rng1, rvals))
return NULL_TREE;
if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST)
@@ -3761,10 +3765,7 @@ gimple_call_alloc_size (gimple *stmt)
of the upper bounds as a constant. Ignore anti-ranges. */
tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node;
wide_int rng2[2];
- if (TREE_CODE (n) == INTEGER_CST)
- rng2[0] = rng2[1] = wi::to_wide (n);
- else if (TREE_CODE (n) != SSA_NAME
- || get_range_info (n, rng2, rng2 + 1) != VR_RANGE)
+ if (!get_range (n, rng2, rvals))
return NULL_TREE;
/* Extend to the maximum precsion to avoid overflow. */
@@ -3802,7 +3803,7 @@ gimple_call_alloc_size (gimple *stmt)
tree
compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
- tree *poff /* = NULL */)
+ tree *poff /* = NULL */, const vr_values *rvals /* = NULL */)
{
tree dummy_decl = NULL_TREE;
if (!pdecl)
@@ -3826,8 +3827,14 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
if (is_gimple_call (stmt))
{
/* If STMT is a call to an allocation function get the size
- from its argument(s). */
- return gimple_call_alloc_size (stmt);
+ from its argument(s). If successful, also set *PDECL to
+ DEST for the caller to include in diagnostics. */
+ if (tree size = gimple_call_alloc_size (stmt))
+ {
+ *pdecl = dest;
+ return size;
+ }
+ return NULL_TREE;
}
if (!is_gimple_assign (stmt))
@@ -3857,13 +3864,13 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
;
else if (wi::ltu_p (wioff, wisiz))
{
- *poff = size_binop (PLUS_EXPR, *poff, off);
+ *poff = *poff ? size_binop (PLUS_EXPR, *poff, off) : off;
return wide_int_to_tree (TREE_TYPE (size),
wi::sub (wisiz, wioff));
}
else
{
- *poff = size_binop (PLUS_EXPR, *poff, off);
+ *poff = *poff ? size_binop (PLUS_EXPR, *poff, off) : off;
return size_zero_node;
}
}
@@ -3888,15 +3895,15 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
;
else if (wi::ltu_p (min, wisiz))
{
- *poff = size_binop (PLUS_EXPR, *poff,
- wide_int_to_tree (sizetype, min));
+ tree t = wide_int_to_tree (sizetype, min);
+ *poff = *poff ? size_binop (PLUS_EXPR, *poff, t) : t;
return wide_int_to_tree (TREE_TYPE (size),
wi::sub (wisiz, min));
}
else
{
- *poff = size_binop (PLUS_EXPR, *poff,
- wide_int_to_tree (sizetype, min));
+ tree t = wide_int_to_tree (sizetype, min);
+ *poff = *poff ? size_binop (PLUS_EXPR, *poff, t) : t;
return size_zero_node;
}
}
@@ -3926,10 +3933,19 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
&& *poff && integer_zerop (*poff))
return size_zero_node;
- /* A valid offset into a declared object cannot be negative. */
- if (tree_int_cst_sgn (*poff) < 0)
+ /* A valid offset into a declared object cannot be negative.
+ A zero size with a zero "inner" offset is still zero size
+ regardless of the "other" offset OFF. */
+ if (*poff
+ && ((integer_zerop (*poff) && integer_zerop (size))
+ || (TREE_CODE (*poff) == INTEGER_CST
+ && tree_int_cst_sgn (*poff) < 0)))
return size_zero_node;
+ wide_int offrng[2];
+ if (!get_range (off, offrng, rvals))
+ return NULL_TREE;
+
/* Adjust SIZE either up or down by the sum of *POFF and OFF
above. */
if (TREE_CODE (dest) == ARRAY_REF)
@@ -3938,24 +3954,28 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
tree eltype = TREE_TYPE (dest);
tree tpsize = TYPE_SIZE_UNIT (eltype);
if (tpsize && TREE_CODE (tpsize) == INTEGER_CST)
- off = fold_build2 (MULT_EXPR, size_type_node, off, tpsize);
+ {
+ wide_int wsz = wi::to_wide (tpsize, offrng->get_precision ());
+ offrng[0] *= wsz;
+ offrng[1] *= wsz;
+ }
else
return NULL_TREE;
}
- wide_int offrng[2];
- if (TREE_CODE (off) == INTEGER_CST)
- offrng[0] = offrng[1] = wi::to_wide (off);
- else if (TREE_CODE (off) == SSA_NAME)
+ if (!*poff)
{
- wide_int min, max;
- enum value_range_kind rng
- = get_range_info (off, offrng, offrng + 1);
- if (rng != VR_RANGE)
- return NULL_TREE;
+ /* If the "inner" offset is unknown and the "outer" offset
+ is either negative or less than SIZE, return the size
+ minus the offset. This may be overly optimistic in
+ the first case if the inner offset happens to be less
+ than the absolute value of the outer offset. */
+ off = fold_convert (ptrdiff_type_node, off);
+ if (tree_int_cst_sgn (off) < 0
+ || tree_int_cst_lt (off, size))
+ return fold_build2 (MINUS_EXPR, size_type_node, size, off);
+ return integer_zero_node;
}
- else
- return NULL_TREE;
/* Convert to the same precision to keep wide_int from "helpfuly"
crashing whenever it sees other argumments. */
@@ -4022,6 +4042,9 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
return component_ref_size (dest);
}
+ if (TREE_CODE (dest) == VAR_DECL)
+ return DECL_SIZE_UNIT (dest);
+
if (TREE_CODE (dest) != ADDR_EXPR)
return NULL_TREE;
@@ -133,8 +133,12 @@ extern tree fold_call_stmt (gcall *, bool);
extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
extern bool is_simple_builtin (tree);
extern bool is_inexpensive_builtin (tree);
-extern tree gimple_call_alloc_size (gimple *);
-extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL);
+
+class vr_values;
+tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
+ const vr_values * = NULL);
+extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
+ const vr_values * = NULL);
extern bool readonly_data_expr (tree exp);
extern bool init_target_chars (void);
@@ -731,10 +731,16 @@ void test_strcpy_range (void)
r = SR (3, DIFF_MAX - 3);
T (8, "01", a + r, a);
- T (8, "012", a + r, a); /* { dg-warning "accessing 4 bytes at offsets \\\[3, \[0-9\]+] and 0 may overlap 1 byte at offset 3" "strcpy" } */
+
+ /* The accesses below might trigger either
+ -Wrestrict: accessing 4 bytes at offsets [3, \[0-9\]+] and 0 may overlap 1 byte at offset 3
+ or
+ -Wstringop-overflow: writing 4 bytes into a region of size 0
+ Either of the two is appropriate. */
+ T (8, "012", a + r, a); /* { dg-warning "\\\[-Wrestrict|-Wstringop-overflow" } */
r = SR (DIFF_MAX - 2, DIFF_MAX - 1);
- T (8, "012", a + r, a); /* { dg-warning "accessing 4 bytes at offsets \\\[\[0-9\]+, \[0-9\]+] and 0 overlaps" "strcpy" } */
+ T (8, "012", a + r, a); /* { dg-warning "\\\[-Wrestrict|-Wstringop-overflow" } */
/* Exercise the full range of ptrdiff_t. */
r = signed_value ();
@@ -3,7 +3,7 @@
Test to verify that past-the-end accesses by string functions to member
arrays by-reference objects are diagnosed.
{ dg-do compile }
- { dg-options "-O2 -Wall -Wno-unused-local-typedefs -ftrack-macro-expansion=0" } */
+ { dg-options "-O2 -Wall -Wno-unused-local-typedefs -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
#define SA(expr) typedef int StaticAssert [2 * !!(expr) - 1]
@@ -1,7 +1,7 @@
/* PR middle-end/91830 - Bogus -Warray-bounds on strcpy into a member
of a subobject compiling binutils
{ dg-do compile }
- { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
+ { dg-options "-O2 -Wall -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
extern char* strcpy (char*, const char*);
extern void sink (void*);
@@ -22,15 +22,15 @@ test (void)
strcpy (p, "Hello");
p = malloc1 (6);
strcpy (p, "Hello");
- strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
+ strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */
p = malloc2 (__INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 6);
strcpy (p, "World");
- strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
+ strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */
p = calloc1 (2, 5);
strcpy (p, "World");
- strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
+ strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */
p = calloc2 (2, __INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 5);
strcpy (p, "World");
- strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
+ strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */
}
@@ -99,7 +99,7 @@ void* xref12 (int);
void* call_xref12 (void)
{
void *p = xref12 (3);
- __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" } */
return p;
}
@@ -197,7 +197,7 @@ void* falias_malloc (void);
void* call_falias_malloc (void)
{
char *p = falias_malloc ();
- __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+ __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" } */
return p;
}
@@ -110,7 +110,7 @@ void test_memop_warn_alloc (const void *src)
struct A *a = __builtin_malloc (sizeof *a * 2);
- memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" } */
+ memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 " "memcpy into allocated" } */
escape (a, src);
/* At -Wstringop-overflow=1 the destination is considered to be
@@ -127,7 +127,7 @@ void test_memop_warn_alloc (const void *src)
struct B *b = __builtin_malloc (sizeof *b * 2);
- memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" "memcpy into allocated" } */
+ memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 " "memcpy into allocated" } */
escape (b);
/* The following idiom of clearing multiple members of a struct is
@@ -9,11 +9,11 @@
unsigned n0, n1;
void*
-keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b)
+keep_strlen_calloc_store_cst_memset (int i, unsigned a, unsigned b)
{
char *p = __builtin_calloc (a, 1);
- p[1] = 'x';
+ p[i] = 'x';
__builtin_memset (p, 0, b);
@@ -23,11 +23,11 @@ keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b)
}
void*
-keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b)
+keep_strlen_calloc_store_var_memset (int i, int x, unsigned a, unsigned b)
{
char *p = __builtin_calloc (a, 1);
- p[1] = x;
+ p[i] = x;
__builtin_memset (p, 0, b);
@@ -37,11 +37,11 @@ keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b)
}
void*
-keep_strlen_calloc_store_memset_2 (int x, unsigned a, unsigned b, unsigned c)
+keep_strlen_calloc_store_memset_2 (int i, int x, unsigned a, unsigned b, unsigned c)
{
char *p = __builtin_calloc (a, 1);
- p[1] = x;
+ p[i] = x;
__builtin_memset (p, 0, b);
n0 = __builtin_strlen (p);
@@ -10,3 +10,5 @@ b ()
a (c);
a (c);
}
+
+// { dg-prune-output "\\\[-Wstringop-overflow" }
@@ -25,8 +25,10 @@ extern bool is_strlen_related_p (tree, tree);
extern bool maybe_diag_stxncpy_trunc (gimple_stmt_iterator, tree, tree);
extern tree set_strlen_range (tree, wide_int, wide_int, tree = NULL_TREE);
-struct c_strlen_data;
class vr_values;
+extern tree get_range (tree, wide_int[2], const vr_values * = NULL);
+
+struct c_strlen_data;
extern void get_range_strlen_dynamic (tree , c_strlen_data *, const vr_values *);
/* APIs internal to strlen pass. Defined in in gimple-ssa-sprintf.c. */
new file mode 100644
@@ -0,0 +1,97 @@
+/* PR middle-end/92341 - missing -Warray-bounds indexing past the end
+ of a compound literal
+ { dg-do compile }
+ { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
+
+#include "range.h"
+
+#define INT_MAX __INT_MAX__
+#define INT_MIN (-__INT_MAX__ - 1)
+
+void sink (int, ...);
+
+
+#define T(...) sink (__LINE__, (__VA_ARGS__))
+
+
+void direct_idx_cst (void)
+{
+ T ((int[]){ }[-1]); // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[0]'" }
+ T ((int[]){ }[0]); // { dg-warning "array subscript 0 is outside array bounds of 'int\\\[0]'" }
+ T ((int[]){ }[1]); // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[0]'" }
+
+ T ((int[]){ 1 }[-1]); // { dg-warning "array subscript -1 is below array bounds of 'int\\\[1]'" }
+ T ((int[]){ 1 }[0]);
+ T ((int[]){ 1 }[1]); // { dg-warning "array subscript 1 is above array bounds of 'int\\\[1]'" }
+ T ((int[]){ 1 }[INT_MIN]); // { dg-warning "array subscript -\[0-9\]+ is below array bounds of 'int\\\[1]'" }
+ T ((int[]){ 1 }[INT_MAX]); // { dg-warning "array subscript \[0-9\]+ is above array bounds of 'int\\\[1]'" }
+ T ((int[]){ 1 }[SIZE_MAX]); // { dg-warning "array subscript \[0-9\]+ is above array bounds of 'int\\\[1]'" }
+}
+
+
+void direct_idx_var (int i)
+{
+ T ((char[]){ }[i]); // { dg-warning "array subscript i is outside array bounds of 'char\\\[0]'" }
+ T ((int[]){ }[i]); // { dg-warning "array subscript i is outside array bounds of 'int\\\[0]'" }
+}
+
+
+void direct_idx_range (void)
+{
+ ptrdiff_t i = SR (-2, -1);
+
+ T ((int[]){ 1 }[i]); // { dg-warning "array subscript \[ \n\r]+ is outside array bounds of 'int\\\[0]'" "pr?????" { xfail *-*-* } }
+}
+
+
+#undef T
+#define T(idx, ...) do { \
+ int *p = (__VA_ARGS__); \
+ sink (p[idx]); \
+ } while (0)
+
+void ptr_idx_cst (void)
+{
+ T (-1, (int[]){ }); // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[0]'" }
+ T ( 0, (int[]){ }); // { dg-warning "array subscript 0 is outside array bounds of 'int\\\[0]'" }
+ T (+1, (int[]){ }); // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[0]'" }
+
+ T (-1, (int[]){ 1 }); // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[1]'" }
+ T ( 0, (int[]){ 1 });
+ T (+1, (int[]){ 1 }); // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[1]'" }
+ T (INT_MIN, (int[]){ 1 }); // { dg-warning "array subscript -\[0-9\]+ is outside array bounds of 'int\\\[1]'" "lp64" { xfail ilp32 } }
+ T (INT_MAX, (int[]){ 1 }); // { dg-warning "array subscript \[0-9\]+ is outside array bounds of 'int\\\[1]'" "lp64" { target lp64 } }
+ // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[1]'" "ilp32" { target ilp32 } .-1 }
+ T (SIZE_MAX, (int[]){ 1 }); // { dg-warning "array subscript -?\[0-9\]+ is outside array bounds of 'int\\\[1]'" }
+}
+
+
+void ptr_idx_var (int i)
+{
+ T (i, (int[]){ }); // { dg-warning "array subscript \[^\n\r\]+ is outside array bounds of 'int\\\[0]'" }
+ T (i, (int[]){ 1 });
+ T (i, (int[]){ i, 1 });
+}
+
+void ptr_idx_range (void)
+{
+ ptrdiff_t i = SR (-2, -1);
+
+ T (i, (int[]){ }); // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[0]'" }
+ T (i, (int[]){ 1 }); // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[1]'" }
+ T (i, (int[]){ i }); // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[1]'" }
+
+ i = SR (0, 1);
+
+ T (i, (int[]){ }); // { dg-warning "array subscript \\\[0, 1] is outside array bounds of 'int\\\[0]'" }
+ T (i, (int[]){ 1 });
+
+ i = SR (1, 2);
+ T (i, (int[]){ 1 }); // { dg-warning "array subscript \\\[1, 2] is outside array bounds of 'int\\\[1]'" }
+
+ i = SR (2, 3);
+ T (i, (int[]){ 1, 2, 3 });
+
+ i = SR (3, 4);
+ T (i, (int[]){ 2, 3, 4 }); // { dg-warning "array subscript \\\[3, 4] is outside array bounds of 'int\\\[3]'" }
+}
new file mode 100644
@@ -0,0 +1,390 @@
+/* PR middle-end/91582 - missing heap overflow detection for strcpy
+ { dg-do compile }
+ { dg-options "-O2 -Wall -Wno-array-bounds -Wno-memset-transposed-args -ftrack-macro-expansion=0" } */
+
+#include "range.h"
+
+#define INT_MAX __INT_MAX__
+#define INT_MIN (-INT_MAX - 1)
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* calloc (size_t, size_t);
+extern void* malloc (size_t);
+extern void* memset (void*, int, size_t);
+extern char* strcpy (char*, const char*);
+extern size_t strlen (const char*);
+
+void sink (void*);
+
+#define T(N, ACCESS) \
+ do { \
+ char *p = ALLOC (N); \
+ ACCESS; \
+ sink (p); \
+ } while (0)
+
+
+void test_byte_store_malloc (void)
+{
+#define ALLOC __builtin_malloc
+
+ T (1, p[-1] = 0);
+ // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 }
+ // { dg-message "at offset -1 to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 }
+
+ T (1, p[ 0] = 0);
+
+ T (1, p[ 1] = 0);
+ // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 }
+ // { dg-message "at offset 1 to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 }
+
+ T (1, p[ 2] = 0);
+ // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 }
+ // { dg-message "at offset 2 to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 }
+
+ T (9, p[-2] = 0);
+ // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 }
+ // { dg-message "at offset -2 to an object with size 9 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 }
+
+ T (9, p[ 0] = 0);
+ T (9, p[ 8] = 0);
+ T (9, p[ 9] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ // Exercise boundary conditions.
+ T (1, p[DIFF_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ // { dg-message "at offset -\[1-9\]\[0-9\]+ to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-1 }
+ T (1, p[INT_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[DIFF_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[SIZE_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ size_t i = UR (3, 5);
+
+ T (1, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ // { dg-message "at offset \\\[3, 5] to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-1 }
+ T (3, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (4, p[i] = 0);
+
+ size_t n = UR (6, 7);
+
+ T (n, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ // { dg-message "at offset -1 to an object with size between 6 and 7 allocated by '__builtin_malloc'" "note" { target *-*-* } .-1 }
+ T (n, p[ 0] = 0);
+ T (n, p[ 6] = 0);
+ T (n, p[ 7] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ T (n, p[i] = 0);
+ T (n, p[i + 3] = 0);
+ T (n, p[i + 4] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ // { dg-message "at offset \\\[7, 9] to an object with size between 6 and 7 allocated by '__builtin_malloc' here" "note" { target *-*-* } .-1 }
+ T (n + 1, p[i + 4] = 0);
+
+ char s[] = "123";
+ n = strlen (s);
+ T (n, p[n] = 0); // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } }
+ // { dg-message "at offset 3 to an object with size 3 allocated by '__builtin_malloc' here" "note" { xfail *-*-* } .-1 }
+}
+
+
+void test_byte_store_alloca (void)
+{
+#undef ALLOC
+#define ALLOC __builtin_alloca
+
+ T (1, p[-1] = 0); // { dg-warning "writing 1 byte into a region of size 0 " }
+ T (1, p[ 0] = 0);
+ T (1, p[ 1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[ 2] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ T (9, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (9, p[ 0] = 0);
+ T (9, p[ 8] = 0);
+ T (9, p[ 9] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ // Exercise boundary conditions.
+ T (1, p[DIFF_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[INT_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[DIFF_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[SIZE_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ size_t i = UR (3, 5);
+
+ T (1, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (3, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (4, p[i] = 0);
+
+ size_t n = UR (6, 7);
+
+ T (n, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (n, p[ 0] = 0);
+ T (n, p[ 6] = 0);
+ T (n, p[ 7] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ char s[] = "1234";
+ n = strlen (s);
+ T (n, p[n] = 0); // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } }
+ // { dg-message "at offset 4 to an object with size 4 allocated by '__builtin_alloca' here" "note" { xfail *-*-* } .-1 }
+}
+
+
+void test_byte_store_vla (void)
+{
+ {
+ size_t n = 3;
+ char vla[n]; // { dg-message "at offset 3 to object 'vla\[^\\n\\r\'\]*' with size 3 declared here" "note" }
+ vla[3] = 0; // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" }
+ sink (vla);
+ }
+
+#define VLA(N, ACCESS) \
+ do { \
+ size_t nelts = N; \
+ char a[nelts]; \
+ ACCESS; \
+ sink (a); \
+ } while (0)
+
+ VLA (1, a[-2] = 0); // { dg-warning "writing 1 byte into a region of size 0 " }
+ VLA (1, a[-1] = 0); // { dg-warning "writing 1 byte into a region of size 0 " }
+ VLA (1, a[ 0] = 0);
+ VLA (1, a[ 1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (1, a[ 2] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ VLA (9, a[-10] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ // The following fails because the no-warning bit is set on the store
+ // for some unknown reason.
+ VLA (9, a[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (9, a[ 0] = 0);
+ VLA (9, a[ 8] = 0);
+ VLA (9, a[ 9] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ // Exercise boundary conditions.
+ VLA (1, a[DIFF_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (1, a[INT_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (1, a[DIFF_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (1, a[SIZE_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ {
+ int i = SR (-5, -1);
+
+ {
+ size_t n = 3;
+ char vla[n]; // { dg-message "at offset \\\[-5, -1] to object 'vla\[^\n\r\]*' with size 3 declared here" "note" }
+ vla[i] = 0; // { dg-warning "writing 1 byte into a region of size 0" }
+ sink (vla);
+ }
+
+ VLA (1, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (3, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (4, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ }
+
+ {
+ int i = SR (3, 5);
+
+ {
+ size_t n = 3;
+ char vla[n]; // { dg-message "at offset \\\[3, 5] to object 'vla\[^\n\r\]*' with size 3 declared here" "note" }
+ vla[i] = 0; // { dg-warning "writing 1 byte into a region of size 0" }
+ sink (vla);
+ }
+
+ VLA (1, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (3, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (4, a[i] = 0);
+ }
+
+ {
+ size_t i = UR (3, 5);
+
+ {
+ size_t n = 3;
+ char vla[n]; // { dg-message "at offset \\\[3, 5] to object 'vla\[^\n\r\]*' with size 3 declared here" "note" }
+ vla[i] = 0; // { dg-warning "writing 1 byte into a region of size 0" }
+ sink (vla);
+ }
+
+ VLA (1, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (3, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (4, a[i] = 0);
+ }
+
+ size_t n = UR (6, 7);
+
+ VLA (n, a[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ VLA (n, a[ 0] = 0);
+ VLA (n, a[ 6] = 0);
+ VLA (n, a[ 7] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+void test_byte_store_vla_strlen (void)
+{
+ char s[] = "12345";
+ size_t n = strlen (s);
+ VLA (n, a[n] = 0); // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } }
+ // { dg-message "at offset 5 to an object with size 5 declared here" "note" { xfail *-*-* } .-1 }
+}
+
+
+void test_byte_store_alloc_size (void)
+{
+ extern __attribute__ ((alloc_size (1), malloc)) void*
+ alloc_1_size (size_t);
+
+ {
+ size_t n = 3;
+ char *p = alloc_1_size (n); // { dg-message "at offset 3 to an object with size 3 allocated by 'alloc_1_size' here" "note" }
+ p[3] = 0; // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" }
+ sink (p);
+ }
+
+ extern __attribute__ ((alloc_size (2), malloc)) void*
+ alloc_2_int (size_t, int);
+
+ {
+ int n = 5;
+ char *p = alloc_2_int (9, n);
+ p[4] = 0;
+ sink (p);
+ }
+
+ extern __attribute__ ((alloc_size (2), malloc)) void*
+ alloc_2_int (size_t, int);
+
+ {
+ int n = 5;
+ char *p = alloc_2_int (9, n);
+ p[4] = 0;
+ // The access below was not diagnosed because GCC didn't recognize
+ // that the pointer returned by malloc functions doesn't alias any
+ // live object in the process. I.e., it assumed that the store to
+ // p[4] above modified p itself. This is pr87313 but now it is
+ // diagnosed so something might be off with the analysis.
+ p[5] = 0; // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" }
+ sink (p);
+ }
+
+ {
+ int n = 5;
+ char *p = alloc_2_int (9, n); // { dg-message "at offset 5 to an object with size 5 allocated by 'alloc_2_int' here" "note" }
+ p[5] = 0; // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" }
+ sink (p);
+ }
+
+#undef ALLOC
+#define ALLOC alloc_1_size
+
+ T (1, p[-1] = 0); // { dg-warning "writing 1 byte into a region of size 0 " }
+ T (1, p[ 0] = 0);
+ T (1, p[ 1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[ 2] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ T (9, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (9, p[ 0] = 0);
+ T (9, p[ 8] = 0);
+ T (9, p[ 9] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ // Exercise boundary conditions.
+ T (1, p[DIFF_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[INT_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[DIFF_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (1, p[SIZE_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ size_t i = UR (3, 5);
+
+ T (1, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (3, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (4, p[i] = 0);
+
+ size_t n = UR (6, 7);
+
+ T (n, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+ T (n, p[ 0] = 0);
+ T (n, p[ 6] = 0);
+ T (n, p[ 7] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+
+void test_memset_malloc (void)
+{
+#undef ALLOC
+#define ALLOC __builtin_malloc
+
+ T (0, memset (p, 0, 0));
+ T (0, memset (p, 0, 1)); // { dg-warning "writing 1 byte into a region of size 0 " }
+
+ T (0, memset (p, 1, 0));
+ T (0, memset (p, 1, 1)); // { dg-warning "writing 1 byte into a region of size 0 " }
+
+ T (3, memset (p, 2, 0));
+ T (3, memset (p, 2, 1));
+ T (3, memset (p, 2, 3));
+ T (3, memset (p, 2, 4)); // { dg-warning "writing 4 bytes into a region of size 3 " }
+
+ T (3, memset (p + 1, 2, 1));
+ T (3, memset (p + 1, 3, 2));
+ T (3, memset (p + 1, 4, 3)); // { dg-warning "writing 3 bytes into a region of size 2 " }
+
+ T (4, memset (&p[2], 2, 2));
+ T (4, memset (&p[2], 4, 3)); // { dg-warning "writing 3 bytes into a region of size 2 " }
+
+ T (3, memset (p, 5, INT_MAX)); // { dg-warning "\\\[-Wstringop-overflow" }
+
+ size_t n = UR (3, 5);
+
+ T (n, memset (p, 6, 0));
+ T (n, memset (p, 7, 1));
+ T (n, memset (p, 8, 3));
+ T (n, memset (p, 9, 4));
+ T (n, memset (p, 0, 5));
+ T (n, memset (p, 1, 6)); // { dg-warning "writing 6 bytes into a region of size between 3 and 5" }
+
+ int i = SR (-32767, 32767);
+ T (n, memset (p + i, 0, 1));
+ T (n, memset (p + i, 0, 3));
+ T (n, memset (p + i, 0, 5));
+ T (n, memset (p + i, 0, 6)); // { dg-warning "writing 6 bytes into a region of size between 3 and 5" }
+}
+
+
+#define S(N) ("0123456789" + 10 - N)
+
+const char str0[] = "";
+const char str1[] = "0";
+const char str2[] = "01";
+
+void test_strcpy_malloc (const void *s, size_t n)
+{
+ const char s0[] = "";
+ const char s1[] = "0";
+ const char s2[] = "01";
+
+ // Each of the strcpy calls below is represented differently. Verify
+ // that they are all handled.
+ // __builtin_memcpy (p_6, "", 1);
+ T (0, strcpy (p, "")); // { dg-warning "writing 1 byte into a region of size 0 " }
+ // MEM[(char * {ref-all})p_10] = 0;
+ T (0, strcpy (p, S (0))); // { dg-warning "writing 1 byte into a region of size 0 " }
+ // MEM[(char * {ref-all})p_14] = MEM[(char * {ref-all})&str0];
+ T (0, strcpy (p, str0)); // { dg-warning "writing 1 byte into a region of size 0 " }
+ // s0 = "";
+ // __builtin_memcpy (p_18, &s0, 1);
+ T (0, strcpy (p, s0)); // { dg-warning "writing 1 byte into a region of size 0 " }
+
+ T (1, strcpy (p, ""));
+ T (1, strcpy (p, S (0)));
+ T (1, strcpy (p, str0));
+ T (1, strcpy (p, s0));
+
+ T (1, strcpy (p, "1")); // { dg-warning "writing 2 bytes into a region of size 1 " }
+ // __builtin_memcpy (p_42, &MEM <char[11]> [(void *)"0123456789" + 9B], 2);
+ T (1, strcpy (p, S (1))); // { dg-warning "writing 2 bytes into a region of size 1 " }
+ // MEM <unsigned char[2]> [(char * {ref-all})p_46] = MEM <unsigned char[2]> [(char * {ref-all})&str1];
+ T (1, strcpy (p, str1)); // { dg-warning "writing 2 bytes into a region of size 1 " }
+ T (1, strcpy (p, s1)); // { dg-warning "writing 2 bytes into a region of size 1 " }
+
+ T (2, strcpy (p, S (0)));
+ T (2, strcpy (p, S (2))); // { dg-warning "writing 3 bytes into a region of size 2 " }
+ T (2, strcpy (p, str2)); // { dg-warning "writing 3 bytes into a region of size 2 " }
+ T (2, strcpy (p, s2)); // { dg-warning "writing 3 bytes into a region of size 2 " }
+}
new file mode 100644
@@ -0,0 +1,293 @@
+/* PR middle-end/91582 - missing heap overflow detection for strcpy
+ PR middle-end/85484 - missing -Wstringop-overflow for strcpy with
+ a string of non-const length
+ { dg-do compile }
+ { dg-options "-O2 -Wall -Wno-array-bounds" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* calloc (size_t, size_t);
+extern void* malloc (size_t);
+extern void* memcpy (void*, const void*, size_t);
+extern void* memset (void*, int, size_t);
+extern char* strcpy (char*, const char*);
+extern size_t strlen (const char*);
+
+void sink (void*);
+
+
+void test_memcpy_nowarn (const void *s, int i, size_t n)
+{
+ sink (memcpy (calloc (1, 1), s, 1));
+ sink (memcpy (calloc (1, 2), s, 1));
+ sink (memcpy (calloc (2, 1), s, 1));
+ sink (memcpy (calloc (3, 1), s, 2));
+ sink (memcpy (calloc (3, 1), "12", 2));
+ sink (memcpy (calloc (3, 1), s, 3));
+ sink (memcpy (calloc (3, 1), "12", 3));
+ sink (memcpy (calloc (i, 1), s, 1));
+ sink (memcpy (calloc (n, 1), s, 1));
+ sink (memcpy (calloc (1, n), "", 1));
+ sink (memcpy (calloc (1, i), "", 1));
+ sink (memcpy (calloc (i, 1), "123", 3));
+ sink (memcpy (calloc (n, 1), "123", 3));
+ sink (memcpy (calloc (1, i), "123456", 7));
+ sink (memcpy (calloc (1, n), "123456", 7));
+ sink (memcpy (calloc (n, 1), s, 12345));
+ sink (memcpy (calloc (1, n), s, n - 1));
+ sink (memcpy (calloc (n, 1), s, n));
+
+ sink (memcpy ((char*)calloc (1, 1) + i, "123", 1));
+ sink (memcpy ((char*)calloc (n, 1) + i, "123", n));
+
+ sink (memcpy ((char*)calloc (1, 1) + i, s, 1));
+ sink (memcpy ((char*)calloc (n, 1) + i, s, n));
+
+ sink (memcpy (malloc (1), s, 1));
+ sink (memcpy (malloc (2), s, 1));
+ sink (memcpy (malloc (3), s, 2));
+ sink (memcpy (malloc (3), "12", 2));
+ sink (memcpy (malloc (3), s, 3));
+ sink (memcpy (malloc (3), "12", 3));
+ sink (memcpy (malloc (n), s, 1));
+ sink (memcpy (malloc (n), "", 1));
+ sink (memcpy (malloc (n), "123", 3));
+ sink (memcpy (malloc (n), "123456", 7));
+ sink (memcpy (malloc (n), s, 12345));
+ sink (memcpy (malloc (n), s, n - 1));
+ sink (memcpy (malloc (n), s, n));
+
+ {
+ const int a[] = { 1, 2, 3, 4 };
+ void *p = (char*)malloc (sizeof a);
+ memcpy (p, a, sizeof a);
+ sink (p);
+ }
+
+ {
+ const int a[] = { 1, 2, 3, 4, 5 };
+ size_t nelts = sizeof a / sizeof *a;
+ int vla[nelts];
+ memcpy (vla, a, nelts * sizeof *vla);
+ sink (vla);
+ }
+}
+
+
+void test_memcpy_warn (const int *s, size_t n)
+{
+ {
+ void *p = (char*)malloc (0);
+ memcpy (p, s, 1); // { dg-warning "writing 1 byte into a region of size 0" }
+ sink (p);
+ }
+
+ {
+ void *p = (char*)malloc (1);
+ memcpy (p, s, 2); // { dg-warning "writing 2 bytes into a region of size 1" }
+ sink (p);
+ }
+
+ {
+ void *p = (char*)malloc (2);
+ memcpy (p, s, 3); // { dg-warning "writing 3 bytes into a region of size 2" }
+ sink (p);
+ }
+
+ {
+ void *p = (char*)malloc (3);
+ memcpy (p, s, 4); // { dg-warning "writing 4 bytes into a region of size 3" }
+ sink (p);
+ }
+
+ {
+ const int a[] = { 1, 2, 3, 4 };
+ void *p = (char*)malloc (sizeof *a);
+ memcpy (p, a, sizeof a); // { dg-warning "" }
+ sink (p);
+ }
+
+ {
+ const int a[] = { 1, 2, 3, 4, 5 };
+ size_t nelts = sizeof a / sizeof *a;
+ char vla[nelts];
+ memcpy (vla, a, nelts * sizeof *a); // { dg-warning "" }
+ sink (vla);
+ }
+
+ {
+ void *p = malloc (n);
+ memcpy (p, s, n * sizeof *s); // { dg-warning "\\\[-Wstringop-overflow" "" { xfail *-*-* } }
+ sink (p);
+ }
+}
+
+void test_memset_nowarn (int x, size_t n)
+{
+ sink (memset (calloc (1, 1), x, 1));
+ sink (memset (calloc (1, 2), x, 1));
+ sink (memset (calloc (2, 1), x, 1));
+ sink (memset (calloc (3, 1), x, 2));
+ sink (memset (calloc (3, 1), x, 3));
+ sink (memset (calloc (n, 1), x, 1));
+ sink (memset (calloc (n, 1), x, 12345));
+ sink (memset (calloc (1, n), x, n - 1));
+ sink (memset (calloc (n, 1), x, n));
+
+ sink (memset (malloc (1), x, 1));
+ sink (memset (malloc (2), x, 1));
+ sink (memset (malloc (3), x, 2));
+ sink (memset (malloc (3), x, 3));
+ sink (memset (malloc (n), x, 1));
+ sink (memset (malloc (n), x, 12345));
+ sink (memset (malloc (n), x, n - 1));
+ sink (memset (malloc (n), x, n));
+
+ {
+ const int a[] = { 1, 2, 3, 4 };
+ void *p = (char*)malloc (sizeof a);
+ memset (p, x, sizeof a);
+ sink (p);
+ }
+
+ {
+ const int a[] = { 1, 2, 3, 4, 5 };
+ size_t nelts = sizeof a / sizeof *a;
+ int vla[nelts];
+ memset (vla, x, nelts * sizeof *vla);
+ sink (vla);
+ }
+}
+
+
+void test_memset_warn (int x, size_t n)
+{
+ {
+ void *p = (char*)malloc (0);
+ memset (p, x, 1); // { dg-warning "writing 1 byte into a region of size 0" }
+ sink (p);
+ }
+
+ {
+ void *p = (char*)malloc (1);
+ memset (p, x, 2); // { dg-warning "writing 2 bytes into a region of size 1" }
+ sink (p);
+ }
+
+ {
+ void *p = (char*)malloc (2);
+ memset (p, x, 3); // { dg-warning "writing 3 bytes into a region of size 2" }
+ sink (p);
+ }
+
+ {
+ void *p = (char*)malloc (3);
+ memset (p, x, 4); // { dg-warning "writing 4 bytes into a region of size 3" }
+ sink (p);
+ }
+
+ {
+ const int a[] = { 1, 2, 3, 4 };
+ void *p = (char*)malloc (sizeof *a);
+ memset (p, 0, sizeof a); // { dg-warning "" }
+ sink (p);
+ }
+
+ {
+ const int a[] = { 1, 2, 3, 4, 5 };
+ size_t nelts = sizeof a / sizeof *a;
+ char vla[nelts];
+ memset (vla, 0, nelts * sizeof *a); // { dg-warning "" }
+ sink (vla);
+ }
+
+ {
+ void *p = malloc (n);
+ memset (p, x, n * sizeof (int)); // { dg-warning "\\\[-Wstringop-overflow" "" { xfail *-*-* } }
+ sink (p);
+ }
+}
+
+
+void test_strcpy_nowarn (const char *s)
+{
+ {
+ const char a[] = "12";
+ int n = strlen (a);
+ char *t = (char*)calloc (2, n);
+ strcpy (t, a);
+ sink (t);
+ }
+
+ {
+ const char a[] = "123";
+ unsigned n = strlen (a) + 1;
+ char *t = (char*)calloc (n, 1);
+ strcpy (t, a);
+ sink (t);
+ }
+
+ {
+ const char a[] = "1234";
+ size_t n = strlen (a) * 2;
+ char *t = (char*)malloc (n);
+ strcpy (t, a);
+ sink (t);
+ }
+
+ {
+ const char a[] = "1234";
+ size_t len = strlen (a) + 1;
+ char vla[len];
+ strcpy (vla, a);
+ sink (vla);
+ }
+
+ {
+ size_t n = strlen (s) + 1;
+ char *t = (char*)malloc (n);
+ strcpy (t, s);
+ sink (t);
+ }
+}
+
+
+void test_strcpy_warn (const char *s)
+{
+ {
+ const char a[] = "123";
+ /* Verify that using signed int for the strlen result works (i.e.,
+ that the conversion from signed int to size_t doesn't prevent
+ the detection. */
+ int n = strlen (a);
+ char *t = (char*)calloc (n, 1); // { dg-message "at offset 0 to an object with size 3 allocated by 'calloc' here" "calloc note" { xfail *-*-* } }
+ // { dg-message "at offset 0 to an object with size at most 3 allocated by 'calloc' here" "calloc note" { target *-*-* } .-1 }
+ strcpy (t, a); // { dg-warning "writing 4 bytes into a region of size (between 0 and )?3 " }
+
+ sink (t);
+ }
+
+ {
+ const char a[] = "1234";
+ size_t n = strlen (a);
+ char *t = (char*)malloc (n); // { dg-message "at offset 0 to an object with size 4 allocated by 'malloc' here" "malloc note" { xfail *-*-* } }
+ // { dg-message "at offset 0 to an object with size at most 4 allocated by 'malloc' here" "malloc note" { target *-*-* } .-1 }
+ strcpy (t, a); // { dg-warning "writing 5 bytes into a region of size (between 0 and )?4 " }
+ sink (t);
+ }
+
+ // Exercise PR middle-end/85484.
+ {
+ size_t len = strlen (s);
+ char vla[len]; // { dg-message "at offset 0 to an object declared here" "vla note" }
+ strcpy (vla, s); // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" }
+ sink (vla);
+ }
+
+ {
+ size_t n = strlen (s);
+ char *t = (char*)malloc (n); // { dg-message "at offset 0 to an object allocated by 'malloc' here" "malloc note" }
+ strcpy (t, s); // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" }
+ sink (t);
+ }
+}
new file mode 100644
@@ -0,0 +1,32 @@
+/* Simple test to verify that constants computed by the strlen pass
+ are propagated through the CFG and subsequently made available
+ within the pass.
+ { dg-do compile }
+ { dg-options "-O2 -Wall " } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* calloc (size_t, size_t);
+extern void* malloc (size_t);
+extern void* memcpy (void*, const void*, size_t);
+extern void* memset (void*, int, size_t);
+extern char* strcpy (char*, const char*);
+extern size_t strlen (const char*);
+
+void sink (void*);
+
+void test_strlen_memcpy (void)
+{
+ char a[8] = "1234567";
+ char b[7];
+
+ // The value of N is computed to be 7 below. Verify that it's
+ // available in the store to B[N] as well as in B[N + 1].
+ size_t n = strlen (a);
+ memcpy (b, a, n);
+ b[n] = 0; // { dg-warning "writing 1 byte into a region of size 0" "constant propagation" { xfail *-*-* } }
+ sink (b);
+
+ b[n + 1] = 0; // { dg-warning "writing 1 byte into a region of size 0" "constant propagation" { xfail *-*-* } }
+ sink (b);
+}
@@ -61,6 +61,7 @@ along with GCC; see the file COPYING3. If not see
#include "vr-values.h"
#include "gimple-ssa-evrp-analyze.h"
+#pragma GCC optimize ("0")
/* 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). */
@@ -84,14 +85,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 +196,20 @@ 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 . */
-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:
@@ -383,10 +390,10 @@ get_addr_stridx (tree exp, tree ptr, unsigned HOST_WIDE_INT *offset_out,
must not be used in for functions that modify the string. */
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 +472,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 +679,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 +846,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 +895,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 +1219,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 +1318,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 +1612,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,7 +1890,8 @@ 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. */
static void
maybe_warn_overflow (gimple *stmt, tree len,
@@ -1868,82 +1901,144 @@ maybe_warn_overflow (gimple *stmt, tree len,
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 (computed below). */
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 = size_zero_node;
+ destsize = compute_objsize (dest, 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)
+ {
+ gimple *stmt = SSA_NAME_DEF_STMT (destdecl);
+ if (is_gimple_call (stmt))
+ alloc_call = stmt;
+ destdecl = NULL_TREE;
+ }
+
+ if (!get_range (destsize, sizrng, rvals))
{
- wide_int wioff = wi::to_wide (off, offrng->get_precision ());
- offrng[0] += wioff;
- offrng[1] += wioff;
+ /* 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,26 +2056,13 @@ 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 remaining space in the destiation 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)
+ if (destsize)
spcrng[0] = spcrng[1] = wi::zero (spcrng->get_precision ());
else
return;
@@ -1991,7 +2073,8 @@ maybe_warn_overflow (gimple *stmt, tree len,
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 +2175,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,6 +2202,62 @@ maybe_warn_overflow (gimple *stmt, tree len,
offstr, destdecl);
return;
}
+
+ if (!alloc_call)
+ return;
+
+ tree allocfn = gimple_call_fndecl (alloc_call);
+
+ 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 %qD 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 %qD here",
+ offstr, allocfn);
+ }
+ else
+ inform (gimple_location (alloc_call),
+ "at offset %s to an object with size between %wu and %wu "
+ "allocated by %qD here",
+ offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi (), allocfn);
}
/* Convenience wrapper for the above. */
@@ -2243,7 +2384,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,
@@ -2425,7 +2566,8 @@ handle_builtin_strchr (gimple_stmt_iterator *gsi)
memcpy. */
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 +2601,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 +2856,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 +2920,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. */
@@ -3190,31 +3369,34 @@ handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
call. */
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);
+ adjust_last_stmt (olddsi, stmt, false);
+ }
+
+ int idx = get_stridx (src);
+ if (idx == 0)
+ return;
bool full_string_p;
if (idx > 0)
@@ -3611,10 +3793,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,10 +3811,19 @@ 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;
}
@@ -3641,46 +3833,66 @@ handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi)
return true when the call is transformed, false otherwise. */
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 (stmt1);
- if (!valid_builtin_call (stmt1))
+ 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);
+
+ /* Bail when there is no statement associated with the destination
+ (the statement may be null even when SI1->ALLOC is not). */
+ if (!si1->stmt)
+ return false;
+
+ /* Avoid optimizing if store is at a variable offset from the beginning
+ of the allocated object. */
+ if (offrng[0] != 0 || offrng[0] != offrng[1])
+ return false;
+
+ /* Bail when the call writes a non-zero value. */
+ if (!integer_zerop (gimple_call_arg (memset_stmt, 1)))
+ return false;
+
+ /* Let the caller know the memset call cleared the destination. */
+ *zero_write = true;
+
enum built_in_function code1 = DECL_FUNCTION_CODE (callee1);
- 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 +3901,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;
@@ -4438,6 +4650,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 +4689,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 +4907,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;
@@ -5080,12 +5316,18 @@ is_char_type (tree type)
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 +5350,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 +5369,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 +5407,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 +5511,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 +5588,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))