The attached patch extends the strlen pass to detect out-of-bounds accesses to memory allocated by calls to other allocation functions besides calloc and malloc, as well as VLAs, and user-defined functions declared with attribute alloc_size. There is some overlap with the _FORTIFY_SOURCE detection but thanks to the extensive use of ranges, this enhancement detects many more cases of overflow.
The solution primarily improves warnings but some of the changes also improve codegen in some cases as a side-effect. I hope to take better advantage of the optimization opportunities the dynamic memory tracking opens up (and also better buffer overflow and array out-of-bounds detection) in GCC 11. Although the strlen pass already tracks some dynamic memory calls (calloc and malloc) rather than extending the same infrastructure (strinfo::stmt) to others I took the approach of adding a separate data member for the other calls (strinfo::alloc) and tracking those independently. I did this to keep the changes only minimally intrusive. In the future (post GCC 10) it might be worth considering merging both. Besides introducing the new member and making use of it, the rest of the changes were prompted by weaknesses exposed by test cases involving dynamically allocated objects. The patch is intended to apply on top of the two related patches posted last week ([1] and [2]). For all tests to pass also expects the fix for PR 92412 posted earlier today ([3]). Martin [1] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00429.html [2] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00652.html [3] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00800.html
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-object-size.c (compute_builtin_object_size): Clear argument. * 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): 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. * g++.dg/warn/Wstringop-overflow-3.C: Adjust text of expected messages. * 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/Warray-bounds-56.c: New test. * gcc.dg/Wstringop-overflow-23.c: New test. * gcc.dg/Wstringop-overflow-24.c: New test. * gcc.dg/Wstringop-overflow-25.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.dg/strlenopt-91.c: New test. * gcc.dg/strlenopt-92.c: New test. * gcc.target/i386/pr82002-1.c: Prune expected warnings. * gcc.target/i386/pr82002-2a.c: Same. * gcc.target/i386/pr82002-2b.c: Same. diff --git a/gcc/builtins.c b/gcc/builtins.c index 8565fd007e5..ed7bdffa2b2 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -3565,10 +3565,11 @@ 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 */) { if (!stmt) return NULL_TREE; @@ -3615,7 +3616,11 @@ gimple_call_alloc_size (gimple *stmt) tree size = gimple_call_arg (stmt, argidx1); - wide_int rng1[2]; + wide_int rng1_buf[2]; + /* If RNG1 is not set, use the buffer. */ + if (!rng1) + rng1 = rng1_buf; + if (!get_range (size, rng1)) return NULL_TREE; @@ -3641,7 +3646,10 @@ gimple_call_alloc_size (gimple *stmt) rng1[1] = rng1[1] * rng2[1]; tree size_max = TYPE_MAX_VALUE (sizetype); if (wi::gtu_p (rng1[1], wi::to_wide (size_max, prec))) - return size_max; + { + rng1[1] = wi::to_wide (size_max); + return size_max; + } return wide_int_to_tree (sizetype, rng1[1]); } diff --git a/gcc/builtins.h b/gcc/builtins.h index 1c7c4ed9d06..3c494f93779 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -134,7 +134,7 @@ 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); -tree gimple_call_alloc_size (gimple *); +tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL); extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL); extern bool readonly_data_expr (tree exp); diff --git a/gcc/targhooks.c b/gcc/targhooks.c index fee4cc271cd..2af88f4dff3 100644 --- a/gcc/targhooks.c +++ b/gcc/targhooks.c @@ -1415,9 +1415,12 @@ default_ref_may_alias_errno (ao_ref *ref) if (TYPE_UNSIGNED (TREE_TYPE (base)) || TYPE_MODE (TREE_TYPE (base)) != TYPE_MODE (integer_type_node)) return false; - /* The default implementation assumes an errno location - declaration is never defined in the current compilation unit. */ + /* The default implementation assumes an errno location declaration + is never defined in the current compilation unit and be aliased + by a local or read-only variable. */ if (DECL_P (base) + && DECL_EXTERNAL (base) + && !TREE_READONLY (base) && !TREE_STATIC (base)) return true; else if (TREE_CODE (base) == MEM_REF diff --git a/gcc/testsuite/c-c++-common/Wrestrict.c b/gcc/testsuite/c-c++-common/Wrestrict.c index c852b06bbd7..1903f502abd 100644 --- a/gcc/testsuite/c-c++-common/Wrestrict.c +++ b/gcc/testsuite/c-c++-common/Wrestrict.c @@ -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 (); diff --git a/gcc/testsuite/g++.dg/warn/Wstringop-overflow-3.C b/gcc/testsuite/g++.dg/warn/Wstringop-overflow-3.C index 6f008549262..db67136b5e7 100644 --- a/gcc/testsuite/g++.dg/warn/Wstringop-overflow-3.C +++ b/gcc/testsuite/g++.dg/warn/Wstringop-overflow-3.C @@ -12,7 +12,7 @@ void sink (void*); struct Ax { char n; - char a[]; // { dg-message "at offset \[0-2\] from object 'Ax::a' declared here" } + char a[]; // { dg-message "at offset \[0-2\] to object 'Ax::a' declared here" } }; // Verify warning for a definition with no initializer. @@ -93,7 +93,7 @@ NOIPA void gaxx () struct A0 { char n; - char a[0]; // { dg-message "at offset \[0-2\] from object 'A0::a' with size 0 declared here" } + char a[0]; // { dg-message "at offset \[0-2\] to object 'A0::a' with size 0 declared here" } }; // Verify warning for a definition with no initializer. @@ -160,7 +160,7 @@ NOIPA void ga0x () struct A1 { char n; - char a[1]; // { dg-message "at offset \[1-9\] from object 'A1::a' with size 1 declared here" } + char a[1]; // { dg-message "at offset \[1-9\] to object 'A1::a' with size 1 declared here" } }; // Verify warning for a definition with no initializer. @@ -234,7 +234,7 @@ NOIPA void ga1x () struct A1i { char n; - char a[1]; // { dg-message "at offset \[1-9\] from object 'A1i::a' with size 1 declared here" } + char a[1]; // { dg-message "at offset \[1-9\] to object 'A1i::a' with size 1 declared here" } char x; }; @@ -307,7 +307,7 @@ NOIPA void ga1ix () struct Bx { char n; - char a[]; // { dg-message "at offset 0 from object 'Bx::a' declared here" } + char a[]; // { dg-message "at offset 0 to object 'Bx::a' declared here" } // Verify the warning for a constant. Bx () { a[0] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } @@ -332,7 +332,7 @@ NOIPA void gbxi (int i) struct B0 { char n; - char a[0]; // { dg-message "at offset 0 from object 'B0::a' with size 0 declared here" } + char a[0]; // { dg-message "at offset 0 to object 'B0::a' with size 0 declared here" } B0 () { a[0] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } }; @@ -348,7 +348,7 @@ NOIPA void gb0 (void) struct B1 { char n; - char a[1]; // { dg-message "at offset 1 from object 'B1::a' with size 1 declared here" } + char a[1]; // { dg-message "at offset 1 to object 'B1::a' with size 1 declared here" } B1 () { a[1] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } }; @@ -362,7 +362,7 @@ NOIPA void gb1 (void) struct B123 { - char a[123]; // { dg-message "at offset 123 from object 'B123::a' with size 123 declared here" } + char a[123]; // { dg-message "at offset 123 to object 'B123::a' with size 123 declared here" } B123 () { a[123] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } }; @@ -376,7 +376,7 @@ NOIPA void gb123 (void) struct B234 { - char a[234]; // { dg-message "at offset 234 from object 'B234::a' with size 234 declared here" } + char a[234]; // { dg-message "at offset 234 to object 'B234::a' with size 234 declared here" } B234 (int i) { a[i] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } }; diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-46.c b/gcc/testsuite/gcc.dg/Warray-bounds-46.c index 4980f93a470..74e78cbdbe8 100644 --- a/gcc/testsuite/gcc.dg/Warray-bounds-46.c +++ b/gcc/testsuite/gcc.dg/Warray-bounds-46.c @@ -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] diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-47.c b/gcc/testsuite/gcc.dg/Warray-bounds-47.c index 06ad488d1e0..848ef365163 100644 --- a/gcc/testsuite/gcc.dg/Warray-bounds-47.c +++ b/gcc/testsuite/gcc.dg/Warray-bounds-47.c @@ -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*); diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-52.c b/gcc/testsuite/gcc.dg/Warray-bounds-52.c new file mode 100644 index 00000000000..c27e6a494f3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Warray-bounds-52.c @@ -0,0 +1,96 @@ +/* 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]'" } + T (INT_MAX, (int[]){ 1 }); // { dg-warning "array subscript \[0-9\]+ is outside array bounds of 'int\\\[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]'" } +} diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-56.c b/gcc/testsuite/gcc.dg/Warray-bounds-56.c new file mode 100644 index 00000000000..644fcd02cb6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Warray-bounds-56.c @@ -0,0 +1,24 @@ +/* PR middle-end/82612 - missing -Warray-bounds on a non-zero offset + from the address of a non-array object + { dg-do compile } + { dg-options "-O2 -Wall" } */ + +int i; +int f0 (void) +{ + int *p = &i; + return p[2]; // { dg-warning "-Warray-bounds" } +} + +int f1 (void) +{ + int i; + int *p = &i; + return p[2]; // { dg-warning "-Warray-bounds" } +} + +int f2 (int i) +{ + int *p = &i; + return p[2]; // { dg-warning "-Warray-bounds" } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c new file mode 100644 index 00000000000..6eed76a44d9 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c @@ -0,0 +1,389 @@ +/* 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" } + // { dg-message "at offset 3 to an object with size 3 allocated by '__builtin_malloc' here" "note" { target *-*-* } .-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" } + // { dg-message "at offset 4 to an object with size 4 allocated by '__builtin_alloca' here" "note" { target *-*-* } .-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" } + // { dg-message "at offset 5 to an object with size 5 declared here" "note" { target *-*-* } .-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 is not diagnosed because GCC doesn't recognize + // that the pointer returned by malloc functions doesn't alias any + // live object in the process. I.e., it assumes that the store to + // p[4] above modifies p itself. + p[5] = 0; // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" "pr87313" { xfail *-*-*} } + 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 " } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-24.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-24.c new file mode 100644 index 00000000000..1c3401f1206 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-24.c @@ -0,0 +1,290 @@ +/* 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" } + strcpy (t, a); // { dg-warning "writing 4 bytes into a region of size 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" } + strcpy (t, a); // { dg-warning "writing 5 bytes into a region of size 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); + } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-25.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-25.c new file mode 100644 index 00000000000..cf306150738 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-25.c @@ -0,0 +1,16 @@ +/* { dg-do compile } + { dg-options "-O2 -Wall " } */ + +char a[32]; + +void f (int i) +{ + __builtin_sprintf (a + 2, "%i", i); + + char *p = a + 2; + + while (p[0] == 0 && p[1]) + ++p; + + *--p = 'x'; // { dg-bogus "\\\[-Wstringop-overflow" } +} diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size.c b/gcc/testsuite/gcc.dg/attr-alloc_size.c index 7b0dc6e4535..a5f15c2a177 100644 --- a/gcc/testsuite/gcc.dg/attr-alloc_size.c +++ b/gcc/testsuite/gcc.dg/attr-alloc_size.c @@ -1,5 +1,5 @@ /* { dg-do compile } */ -/* { dg-options "-O2 -Wall -Wno-array-bounds -ftrack-macro-expansion=0" } */ +/* { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */ extern void abort (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" } */ } diff --git a/gcc/testsuite/gcc.dg/attr-copy-2.c b/gcc/testsuite/gcc.dg/attr-copy-2.c index f311ca32aa6..cdb4891a0ae 100644 --- a/gcc/testsuite/gcc.dg/attr-copy-2.c +++ b/gcc/testsuite/gcc.dg/attr-copy-2.c @@ -2,7 +2,7 @@ Exercise attribute copy for functions. { dg-do compile } { dg-require-alias "" } - { dg-options "-O2 -Wall -Wno-array-bounds" } */ + { dg-options "-O2 -Wall" } */ #define Assert(expr) typedef char AssertExpr[2 * !!(expr) - 1] @@ -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; } diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c index 320cd51fcf2..f1d46b82a6c 100644 --- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c @@ -1,4 +1,4 @@ -/* Test exercising -Wstringop-overflow warnings. */ +/* Test exercising -Wrawmem-overflow and -Wstringop-overflow warnings. */ /* { dg-do compile } */ /* { dg-options "-O2 -Wstringop-overflow=1" } */ @@ -49,7 +49,7 @@ void test_memop_warn_local (const void *src) memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */ escape (a, src); - /* At -Wstringop-overflow=1 the destination is considered to be + /* At -Wrawmem-overflow=1 the destination is considered to be the whole array and its size is therefore sizeof a. */ memcpy (&a[0], src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" } */ escape (a, src); @@ -110,10 +110,10 @@ 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" } */ escape (a, src); - /* At -Wstringop-overflow=1 the destination is considered to be + /* At -Wrawmem-overflow=1 the destination is considered to be the whole array and its size is therefore sizeof a. */ memcpy (&a[0], src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" } */ escape (a, src); @@ -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" } */ escape (b); /* The following idiom of clearing multiple members of a struct is diff --git a/gcc/testsuite/gcc.dg/strlenopt-86.c b/gcc/testsuite/gcc.dg/strlenopt-86.c index 3e86fa3c90a..d2029443556 100644 --- a/gcc/testsuite/gcc.dg/strlenopt-86.c +++ b/gcc/testsuite/gcc.dg/strlenopt-86.c @@ -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); diff --git a/gcc/testsuite/gcc.dg/strlenopt-91.c b/gcc/testsuite/gcc.dg/strlenopt-91.c new file mode 100644 index 00000000000..1ede65f6e0a --- /dev/null +++ b/gcc/testsuite/gcc.dg/strlenopt-91.c @@ -0,0 +1,40 @@ +/* Test to verify that alloca calls followed by memset to zero out + the allocated object don't end up transformed into calloc. + { dg-do compile } + { dg-options "-O2 -fdump-tree-optimized" } */ + +void sink (void*); + +void alloca_memset_cst (void) +{ + void *p = __builtin_alloca (8); + __builtin_memset (p, 0, 8); + sink (p); +} + +void alloca_memset_var (unsigned n) +{ + void *p = __builtin_alloca (n); + __builtin_memset (p, 0, n); + sink (p); +} + + +void vla_memset_cst (void) +{ + unsigned n = 32; + + char a[n]; + __builtin_memset (a, 0, n); + sink (a); +} + + +void vla_memset_var (unsigned n) +{ + char a[n]; + __builtin_memset (a, 0, n); + sink (a); +} + +/* { dg-final { scan-tree-dump-times "calloc" 0 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/strlenopt-92.c b/gcc/testsuite/gcc.dg/strlenopt-92.c new file mode 100644 index 00000000000..6d947c22025 --- /dev/null +++ b/gcc/testsuite/gcc.dg/strlenopt-92.c @@ -0,0 +1,55 @@ +/* Verify that a strlen() call is eliminated for a pointer to a region + of memory allocated by calloc() even if a byte is written at some + non-zero offset into the region that isn't known to be nul. + { dg-do compile } + { dg-options "-O2 -fdump-tree-optimized" } */ + +unsigned n0, n1; + +void* +keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b) +{ + char *p = __builtin_calloc (a, 1); + + p[1] = 'x'; + + __builtin_memset (p, 0, b); + + n0 = __builtin_strlen (p); + + return p; +} + +void* +keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b) +{ + char *p = __builtin_calloc (a, 1); + + p[1] = x; + + __builtin_memset (p, 0, b); + + n0 = __builtin_strlen (p); + + return p; +} + +void* +keep_strlen_calloc_store_memset_2 (int x, unsigned a, unsigned b, unsigned c) +{ + char *p = __builtin_calloc (a, 1); + + p[1] = x; + __builtin_memset (p, 0, b); + + n0 = __builtin_strlen (p); + + p[3] = x; + __builtin_memset (p, 0, c); + + n1 = __builtin_strlen (p); + + return p; +} + +/* { dg-final { scan-tree-dump-times "__builtin_strlen" 0 "optimized" } } */ diff --git a/gcc/testsuite/gcc.target/i386/pr82002-1.c b/gcc/testsuite/gcc.target/i386/pr82002-1.c index 86678a01992..b4d4bd3d125 100644 --- a/gcc/testsuite/gcc.target/i386/pr82002-1.c +++ b/gcc/testsuite/gcc.target/i386/pr82002-1.c @@ -10,3 +10,5 @@ b () a (c); a (c); } + +// { dg-prune-output "\\\[-Wstringop-overflow" } diff --git a/gcc/testsuite/gcc.target/i386/pr82002-2a.c b/gcc/testsuite/gcc.target/i386/pr82002-2a.c index dbcc105fcd2..66df36ad317 100644 --- a/gcc/testsuite/gcc.target/i386/pr82002-2a.c +++ b/gcc/testsuite/gcc.target/i386/pr82002-2a.c @@ -11,4 +11,4 @@ b () a (c); } -/* { dg-prune-output "\\\[-Wstringop-overflow" } */ +// { dg-prune-output "\\\[-Wstringop-overflow" } diff --git a/gcc/testsuite/gcc.target/i386/pr82002-2b.c b/gcc/testsuite/gcc.target/i386/pr82002-2b.c index e1ad737263c..040f0d0fe96 100644 --- a/gcc/testsuite/gcc.target/i386/pr82002-2b.c +++ b/gcc/testsuite/gcc.target/i386/pr82002-2b.c @@ -11,4 +11,4 @@ b () a (c); } -/* { dg-prune-output "\\\[-Wstringop-overflow" } */ +// { dg-prune-output "\\\[-Wstringop-overflow" } diff --git a/gcc/tree-object-size.c b/gcc/tree-object-size.c index d591c36dea3..0f22860bcaf 100644 --- a/gcc/tree-object-size.c +++ b/gcc/tree-object-size.c @@ -466,7 +466,6 @@ alloc_object_size (const gcall *call, int object_size_type) return unknown[object_size_type]; } - /* If object size is propagated from one of function's arguments directly to its return value, return that argument for GIMPLE_CALL statement CALL. Otherwise return NULL. */ @@ -671,6 +670,7 @@ compute_builtin_object_size (tree ptr, int object_size_type, } *psize = object_sizes[object_size_type][SSA_NAME_VERSION (ptr)]; + *poff = NULL_TREE; return *psize != unknown[object_size_type]; } diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index cdca50aeb79..d03b669d77e 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -85,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; @@ -190,7 +196,8 @@ 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. */ + and returns either the constant value or VAL on success or null + when the range couldn't be determined . */ tree get_range (tree val, wide_int minmax[2], const vr_values *rvals /* = NULL */) @@ -388,10 +395,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) { @@ -470,12 +477,12 @@ 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 iterations and store it in the OFFRNG array. */ - offrng[0] += offset; + offrng[0] += offset; offrng[1] += offset; } else @@ -677,6 +684,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; @@ -843,6 +851,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. */ @@ -890,45 +900,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)) @@ -1202,80 +1224,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; } @@ -1294,6 +1323,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; @@ -1587,6 +1617,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: @@ -1863,7 +1895,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 otherise. */ static void maybe_warn_overflow (gimple *stmt, tree len, @@ -1873,82 +1906,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); + 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); + 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 @@ -1966,26 +2061,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; @@ -1996,7 +2078,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] @@ -2097,6 +2180,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); @@ -2110,18 +2195,74 @@ maybe_warn_overflow (gimple *stmt, tree len, sprintf (offstr, "[%lli, %lli]", (long long) offrng[0].to_shwi (), (long long) offrng[1].to_shwi ()); - if (destdecl && DECL_P (destdecl)) + if (destdecl) { if (tree size = DECL_SIZE_UNIT (destdecl)) inform (DECL_SOURCE_LOCATION (destdecl), - "at offset %s from object %qD with size %E declared here", + "at offset %s to object %qD with size %E declared here", offstr, destdecl, size); else inform (DECL_SOURCE_LOCATION (destdecl), - "at offset %s from object %qD declared here", + "at offset %s to object %qD declared here", 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. */ @@ -2248,7 +2389,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, @@ -2426,7 +2567,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; @@ -2460,6 +2602,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) @@ -2710,26 +2857,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) @@ -2742,7 +2921,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. */ @@ -3191,31 +3370,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) @@ -3612,10 +3794,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); @@ -3629,10 +3812,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; } @@ -3642,46 +3834,61 @@ 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); + + /* 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 (alloc_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); @@ -3690,7 +3897,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; @@ -4433,6 +4640,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; @@ -4449,7 +4679,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. */ @@ -4666,7 +4896,8 @@ count_nonzero_bytes (tree exp, unsigned lenrange[3], bool *nulterm, '*(int*)a = 12345'). Return true when handled. */ 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; @@ -5073,12 +5304,18 @@ is_char_type (tree type) in the CFG 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); + } + if (!flag_optimize_strlen || !strlen_optimize || !valid_builtin_call (stmt)) @@ -5104,7 +5341,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: @@ -5123,18 +5360,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: @@ -5158,7 +5397,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); @@ -5261,6 +5501,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) { @@ -5312,7 +5577,7 @@ 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); @@ -5326,14 +5591,20 @@ check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh, 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; + 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. */