From b631bdb3c16e85f35d38e39b3d315c35e4a5747c Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Thu, 25 Jul 2019 00:29:17 +0000 Subject: [PATCH] PR tree-optimization/91183 - strlen of a strcpy result with a conditional source not folded PR tree-optimization/91183 - strlen of a strcpy result with a conditional source not folded PR tree-optimization/86688 - missing -Wstringop-overflow using a non-string local array in strnlen with excessive bound gcc/ChangeLog: PR tree-optimization/91183 PR tree-optimization/86688 * builtins.c (compute_objsize): Handle MEM_REF. * tree-ssa-strlen.c (class ssa_name_limit_t): New. (get_min_string_length): Remove. (count_nonzero_bytes): New function. (handle_char_store): Rename... (handle_store): to this. Handle multibyte stores via integer types. (strlen_check_and_optimize_stmt): Adjust conditional and the called function name. gcc/testsuite/ChangeLog: PR tree-optimization/91183 PR tree-optimization/86688 * gcc.dg/Wstringop-overflow-14.c: New test. * gcc.dg/attr-nonstring-2.c: Remove xfails. * gcc.dg/strlenopt-70.c: New test. * gcc.dg/strlenopt-71.c: New test. * gcc.dg/strlenopt-72.c: New test. * gcc.dg/strlenopt-8.c: Remove xfails. From-SVN: r273783 --- gcc/ChangeLog | 13 + gcc/builtins.c | 14 + gcc/testsuite/ChangeLog | 12 + gcc/testsuite/c-c++-common/ubsan/object-size-9.c | 2 +- gcc/testsuite/gcc.dg/Wstringop-overflow-14.c | 49 +++ gcc/testsuite/gcc.dg/attr-nonstring-2.c | 8 +- gcc/testsuite/gcc.dg/strlenopt-70.c | 293 +++++++++++++++ gcc/testsuite/gcc.dg/strlenopt-71.c | 212 +++++++++++ gcc/testsuite/gcc.dg/strlenopt-72.c | 64 ++++ gcc/testsuite/gcc.dg/strlenopt-8.c | 8 +- gcc/tree-ssa-strlen.c | 460 ++++++++++++++++++----- 11 files changed, 1035 insertions(+), 100 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-14.c create mode 100644 gcc/testsuite/gcc.dg/strlenopt-70.c create mode 100644 gcc/testsuite/gcc.dg/strlenopt-71.c create mode 100644 gcc/testsuite/gcc.dg/strlenopt-72.c diff --git a/gcc/ChangeLog b/gcc/ChangeLog index ae78a75..45221a4 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -16,6 +16,19 @@ 2019-07-24 Martin Sebor + PR tree-optimization/91183 + PR tree-optimization/86688 + * builtins.c (compute_objsize): Handle MEM_REF. + * tree-ssa-strlen.c (class ssa_name_limit_t): New. + (get_min_string_length): Remove. + (count_nonzero_bytes): New function. + (handle_char_store): Rename... + (handle_store): to this. Handle multibyte stores via integer types. + (strlen_check_and_optimize_stmt): Adjust conditional and the called + function name. + +2019-07-24 Martin Sebor + PR driver/80545 * diagnostic.c (diagnostic_classify_diagnostic): Use lang_mask. (diagnostic_report_diagnostic): Same. diff --git a/gcc/builtins.c b/gcc/builtins.c index e5a9261..695a9d1 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -3652,6 +3652,20 @@ compute_objsize (tree dest, int ostype) if (!ostype) return NULL_TREE; + if (TREE_CODE (dest) == MEM_REF) + { + tree ref = TREE_OPERAND (dest, 0); + tree off = TREE_OPERAND (dest, 1); + if (tree size = compute_objsize (ref, ostype)) + { + if (tree_int_cst_lt (off, size)) + return fold_build2 (MINUS_EXPR, size_type_node, size, off); + return integer_zero_node; + } + + return NULL_TREE; + } + if (TREE_CODE (dest) != ADDR_EXPR) return NULL_TREE; diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 4749b0c..532d1c1 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,5 +1,17 @@ 2019-07-24 Martin Sebor + PR tree-optimization/91183 + PR tree-optimization/86688 + * gcc/testsuite/c-c++-common/ubsan/object-size-9.c: Disable warnings. + * gcc.dg/Wstringop-overflow-14.c: New test. + * gcc.dg/attr-nonstring-2.c: Remove xfails. + * gcc.dg/strlenopt-70.c: New test. + * gcc.dg/strlenopt-71.c: New test. + * gcc.dg/strlenopt-72.c: New test. + * gcc.dg/strlenopt-8.c: Remove xfails. + +2019-07-24 Martin Sebor + PR driver/80545 * gcc.misc-tests/help.exp: Add tests. * lib/options.exp: Handle C++. diff --git a/gcc/testsuite/c-c++-common/ubsan/object-size-9.c b/gcc/testsuite/c-c++-common/ubsan/object-size-9.c index 3684bbe..bd2a53e 100644 --- a/gcc/testsuite/c-c++-common/ubsan/object-size-9.c +++ b/gcc/testsuite/c-c++-common/ubsan/object-size-9.c @@ -1,6 +1,6 @@ /* { dg-do run } */ /* { dg-skip-if "" { *-*-* } { "*" } { "-O2" } } */ -/* { dg-options "-fsanitize=undefined" } */ +/* { dg-options "-Wno-stringop-overflow -fsanitize=undefined" } */ /* Test PARM_DECLs and RESULT_DECLs. */ diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-14.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-14.c new file mode 100644 index 0000000..d23a248 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-14.c @@ -0,0 +1,49 @@ +/* Test to verify that past-the-end multibyte writes via lvalues of wider + types than char are diagnosed. + { dg-do compile } + { dg-require-effective-target int32plus } + { dg-options "-O2 -Wall" } */ + +typedef __INT16_TYPE__ int16_t; +typedef __INT32_TYPE__ int32_t; +typedef __INT64_TYPE__ int64_t; +typedef __SIZE_TYPE__ size_t; + +void* memcpy (void*, const void*, size_t); +char* strcpy (char*, const char*); + +char a4[4], a8[8], a16[16]; + +const char s4[] = "1234"; +const char t4[] = "4321"; + +void test_memcpy_cond (int i) +{ + char *p = a4 + 1; + const char *q = i ? s4 : t4; + memcpy (p, q, 4); // { dg-warning "writing 4 bytes into a region of size 3" } +} + + +void test_int16 (void) +{ + char *p = a4 + 1; + *(int16_t*)p = 0; + *(int16_t*)(p + 2) = 0; // { dg-warning "writing 2 bytes into a region of size 1" } +} + + +void test_int32 (void) +{ + char *p = a8 + 3; + *(int32_t*)p = 0; + *(int32_t*)(p + 2) = 0; // { dg-warning "writing 4 bytes into a region of size 3" } +} + + +void test_int64 (void) +{ + char *p = a16 + 5; + *(int64_t*)p = 0; + *(int64_t*)(p + 5) = 0; // { dg-warning "writing 8 bytes into a region of size 6" } +} diff --git a/gcc/testsuite/gcc.dg/attr-nonstring-2.c b/gcc/testsuite/gcc.dg/attr-nonstring-2.c index 246a372..ef2144d 100644 --- a/gcc/testsuite/gcc.dg/attr-nonstring-2.c +++ b/gcc/testsuite/gcc.dg/attr-nonstring-2.c @@ -73,8 +73,8 @@ void test_strnlen_string_cst (void) T (3, "12", 3, 1); T (3, "12", 3, 9); T (3, "123", 3, 1); - T (3, "123", 3, 4); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 4" "bug 86688" { xfail *-*-* } } */ - T (3, "123", 3, 9); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 9" "bug 86688" { xfail *-*-* } } */ + T (3, "123", 3, 4); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 4" } */ + T (3, "123", 3, 9); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 9" } */ T (5, "1", 2, 1); T (5, "1", 2, 2); @@ -110,6 +110,6 @@ void test_strnlen_string_range (void) { T (3, "1", 2, UR (0, 1)); T (3, "1", 2, UR (3, 9)); - T (3, "123", 3, UR (4, 5)); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound \\\[4, 5]" "bug 86688" { xfail *-*-* } } */ - T (3, "123", 3, UR (5, 9)); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound \\\[5, 9]" "bug 86688" { xfail *-*-* } } */ + T (3, "123", 3, UR (4, 5)); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound \\\[4, 5]" } */ + T (3, "123", 3, UR (5, 9)); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound \\\[5, 9]" } */ } diff --git a/gcc/testsuite/gcc.dg/strlenopt-70.c b/gcc/testsuite/gcc.dg/strlenopt-70.c new file mode 100644 index 0000000..2dc42d6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/strlenopt-70.c @@ -0,0 +1,293 @@ +/* PR tree-optimization/91183 - strlen of a strcpy result with a conditional + source not folded + Test to verify that strlen can determine string lengths from wider stores + than narrow characters. This matters because on targets that can handle + unaligned stores and where GCC lowers multi-character stores into smaller + numbers of wider stores. + { dg-do compile } + { dg-options "-O2 -fdump-tree-optimized" } */ + +#include "strlenopt.h" + +#define CHAR_BIT __CHAR_BIT__ + +typedef __INT16_TYPE__ int16_t; +typedef __INT32_TYPE__ int32_t; +typedef __INT64_TYPE__ int64_t; +typedef __UINT64_TYPE__ uint64_t; + +#define CAT(x, y) x ## y +#define CONCAT(x, y) CAT (x, y) +#define FAILNAME(name) CONCAT (call_ ## name ##_on_line_, __LINE__) + +#define FAIL(name) do { \ + extern void FAILNAME (name) (void); \ + FAILNAME (name)(); \ + } while (0) + +/* Macros to emit a call to function named + call_failed_to_be_eliminated_on_line_NNN() + for each call that's expected to be eliminated. The dg-final + scan-tree-dump-time directive at the bottom of the test verifies + that no such call appears in output. */ +#define ELIM(expr) \ + if ((expr)) FAIL (not_eliminated); else (void)0 + +/* Verify that 'strlen (A) EXPECT' is folded to true. When non-null, + the first sizeof (INIT) - 1 bytes of the INIT arrray are stored + in A first, followed by *(TYPE*)A = ASSIGN. */ +#define T(init, type, off, assign, expect) do { \ + char a[32]; \ + memcpy (a, init ? init : "", init ? sizeof init - 1 : 0); \ + *(type*)(a + off) = assign; \ + ELIM (!(strlen (a) expect)); \ + } while (0) + +/* Same as above but the assignment consisting of the two quadwords + QW1 and QW2 to support int128_t. */ +#define T2(init, type, off, qw0, qw1, expect) do { \ + char a[32]; \ + memcpy (a, init ? init : "", init ? sizeof init - 1: 0); \ + type assign = ((type)qw0 << (sizeof (type) * CHAR_BIT / 2)) | (type)qw1; \ + *(type*)(a + off) = assign; \ + ELIM (!(strlen (a) expect)); \ + } while (0) + +/* Same as T but works around the optimizer dropping the initializing + store before the assignment and defeating the strlen optimization. */ +#define TX(init, type, off, assign, expect) do { \ + char a[32]; \ + strcpy (a, init + 2); \ + strcat (a, init + sizeof (init) - 3); \ + *(type*)(a + off) = assign; \ + ELIM (!(strlen (a) expect)); \ + } while (0) + + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define I16(s) ((s[0] << 8) + s[1]) +# define I32(s) ((s[0] << 24) + (s[1] << 16) + (s[2] << 8) + s[3]) +# define I64(s) \ + (((uint64_t)s[0] << 56) \ + + ((uint64_t)s[1] << 48) \ + + ((uint64_t)s[2] << 40) \ + + ((uint64_t)s[3] << 32) \ + + ((uint64_t)s[4] << 24) \ + + ((uint64_t)s[5] << 16) \ + + ((uint64_t)s[6] << 8) \ + + s[7]) +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define I16(s) ((s[1] << 8) + s[0]) +# define I32(s) ((s[3] << 24) + (s[2] << 16) + (s[1] << 8) + s[0]) +# define I64(s) \ + (((uint64_t)s[7] << 56) \ + + ((uint64_t)s[6] << 48) \ + + ((uint64_t)s[5] << 40) \ + + ((uint64_t)s[4] << 32) \ + + ((uint64_t)s[3] << 24) \ + + ((uint64_t)s[2] << 16) \ + + ((uint64_t)s[1] << 8) \ + + s[0]) +#endif + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + +void store_16bit_be (void) +{ + T ("xxx", int16_t, 0, 0x0001, == 0); + T ("xxx", int16_t, 0, 0x0010, == 0); + T ("xxx", int16_t, 0, 0x0011, == 0); + T ("xxx", int16_t, 0, 0x0100, == 1); + T ("xxx", int16_t, 0, 0x1000, == 1); + T ("xxx", int16_t, 0, 0x1100, == 1); +} + +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + +void store_16bit_le (int i) +{ + int16_t x0000 = I16 ("\0\0"); + int16_t x0001 = 0x0001; + int16_t x0010 = 0x0010; + int16_t x0011 = 0x0011; + int16_t x0100 = 0x0100; + int16_t x1000 = 0x1000; + int16_t x1100 = 0x1100; + + T (0, int16_t, 0, x0000, == 0); + T ("x", int16_t, 0, x0000, == 0); + T ("xx", int16_t, 0, x0000, == 0); + T ("xxxx", int16_t, 0, x0000, == 0); + T (0, int16_t, 0, x0001, == 1); + T ("\0\0\0", int16_t, 0, x0001, == 1); + T (0, int16_t, 0, x0010, == 1); + T ("x\0\0", int16_t, 0, x0010, == 1); + T (0, int16_t, 0, x0011, == 1); + T ("xx\0", int16_t, 0, x0011, == 1); + T (0, int16_t, 0, x0100, == 0); + T ("\0\0\0", int16_t, 0, x0100, == 0); + T (0, int16_t, 0, x1000, == 0); + T ("x\0\0", int16_t, 0, x1000, == 0); + T (0, int16_t, 0, x1100, == 0); + T ("xx\0", int16_t, 0, x1100, == 0); + + // FIXME: This fails because of the next test but succeeds on its own. + // T (0, int16_t, 0, i ? x0001 : x0010, == 1); + T ("xxx", int16_t, 0, i ? x0100 : x1100, == 0); +} + +#endif + +void store_32bit (volatile int i) +{ + T (0, int32_t, 0, 0, == 0); + T ("x", int32_t, 0, 0, == 0); + T ("xx", int32_t, 0, 0, == 0); + T ("xxx", int32_t, 0, 0, == 0); + T ("xxxx", int32_t, 0, 0, == 0); + + T ("\0", int32_t, 1, 0, == 0); + T ("x", int32_t, 1, 0, == 1); + T ("xx", int32_t, 2, 0, == 2); + T ("xxx", int32_t, 3, 0, == 3); + + T ("xxx", int32_t, 0, I32 ("\01\0\0\0"), == 1); + T ("xxx", int32_t, 0, I32 ("\0\01\0\0"), == 0); + T ("xxx", int32_t, 0, I32 ("\0\0\01\0"), == 0); + T ("xxx", int32_t, 0, I32 ("\0\0\0\01"), == 0); + + T ("xxx", int32_t, 0, I32 ("\1\2\0\0"), == 2); + T ("xxx", int32_t, 0, I32 ("\0\1\2\0"), == 0); + T ("xxx", int32_t, 0, I32 ("\0\0\1\2"), == 0); + + T ("xxx", int32_t, 0, I32 ("\1\2\3\0"), == 3); + T ("xxx", int32_t, 0, I32 ("\0\1\2\3"), == 0); + + int32_t x00332211 = I32 ("123\0"); + int32_t x00002211 = I32 ("12\0\0"); + int32_t x00000011 = I32 ("1\0\0\0"); + + T ("xxxx", int32_t, 0, i ? x00332211 : x00002211, <= 3); + T ("xxxx", int32_t, 0, i ? x00332211 : x00002211, >= 2); + T ("xxxx", int32_t, 0, i ? x00332211 : x00000011, <= 3); + T ("xxxx", int32_t, 0, i ? x00332211 : x00000011, >= 1); + + TX ("abcde", int32_t, 0, i ? I32 ("1234") : I32 ("1235"), == 5); + TX ("abcde", int32_t, 1, i ? I32 ("1234") : I32 ("1235"), == 5); + + TX ("abcdef", int32_t, 0, i ? I32 ("1235") : I32 ("1234"), == 6); + TX ("abcdef", int32_t, 1, i ? I32 ("1235") : I32 ("1234"), == 6); + TX ("abcdef", int32_t, 2, i ? I32 ("1235") : I32 ("1234"), == 6); + TX ("abcdef", int32_t, 3, i ? I32 ("124\0") : I32 ("123\0"), == 6); + TX ("abcdef", int32_t, 3, i ? I32 ("12\0\0") : I32 ("13\0\0"), == 5); + + TX ("abcdef", int32_t, 3, i ? I32 ("12\0\0") : I32 ("123\0"), >= 5); + TX ("abcdef", int32_t, 3, i ? I32 ("12\0\0") : I32 ("123\0"), < 7); +} + +void store_64bit (int i) +{ + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\1\0\0\0"), == 1); + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\0\1\0\0"), == 0); + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\0\0\1\0"), == 0); + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\0\00\0\1"), == 0); + T2 ("xxxxxxx", int64_t, 0, I32 ("\1\0\0\0"), 0, == 0); + T2 ("xxxxxxx", int64_t, 0, I32 ("\0\1\0\0"), 0, == 0); + T2 ("xxxxxxx", int64_t, 0, I32 ("\0\0\1\0"), 0, == 0); + T2 ("xxxxxxx", int64_t, 0, I32 ("\0\0\0\1"), 0, == 0); + + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\1\2\0\0"), == 2); + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\0\1\2\0"), == 0); + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\0\0\1\2"), == 0); + + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\1\2\3\0"), == 3); + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\0\1\2\3"), == 0); + + T2 ("xxxxxxx", int64_t, 0, 0, I32 ("\1\2\3\4"), == 4); + T2 ("xxxxxxx", int64_t, 0, I32 ("\5\0\0\0"), I32 ("\1\2\3\4"), == 5); + T2 ("xxxxxxx", int64_t, 0, I32 ("\5\6\0\0"), I32 ("\1\2\3\4"), == 6); + T2 ("xxxxxxx", int64_t, 0, I32 ("\5\6\7\0"), I32 ("\1\2\3\4"), == 7); + + int64_t x7777777 = I64 ("\7\7\7\7\7\7\7"); + int64_t x666666 = I64 ("\6\6\6\6\6\6\0"); + int64_t x4444 = I64 ("\4\4\4\4\0\0\0"); + int64_t x3333 = I64 ("\3\3\3\3\0\0\0"); + int64_t x1 = I64 ("\1\0\0\0\0\0\0"); + + T ("x\0xxxxxx", int64_t, 0, i ? x7777777 : x666666, <= 7); + T ("xx\0xxxxx", int64_t, 0, i ? x7777777 : x666666, >= 6); + T ("xxx\0xxxx", int64_t, 0, i ? x666666 : x1, <= 6); + T ("xxxx\0xxx", int64_t, 0, i ? x666666 : x1, >= 1); + T ("xxxxxx\0x", int64_t, 0, i ? x4444 : x3333, == 4); +} + +#ifdef __uint128_t + +typedef __uint128_t uint128_t; + +void store_128bit (void) +{ + uint64_t x1 = I64 ("\1\0\0\0\0\0\0\0"); + uint64_t x01 = I64 ("\0\1\0\0\0\0\0\0"); + uint64_t x001 = I64 ("\0\0\1\0\0\0\0\0"); + uint64_t x0001 = I64 ("\0\0\0\1\0\0\0\0"); + uint64_t x00001 = I64 ("\0\0\0\0\1\0\0\0"); + uint64_t x000001 = I64 ("\0\0\0\0\0\1\0\0"); + uint64_t x0000001 = I64 ("\0\0\0\0\0\0\1\0"); + uint64_t x00000001 = I64 ("\0\0\0\0\0\0\0\1"); + + T2 ("xxxxxxx", uint128_t, 0, 0, x1, == 1); + T2 ("xxxxxxx", uint128_t, 0, 0, x01, == 0); + T2 ("xxxxxxx", uint128_t, 0, 0, x001, == 0); + T2 ("xxxxxxx", uint128_t, 0, 0, x0001, == 0); + T2 ("xxxxxxx", uint128_t, 0, 0, x00001, == 0); + T2 ("xxxxxxx", uint128_t, 0, 0, x000001, == 0); + T2 ("xxxxxxx", uint128_t, 0, 0, x0000001, == 0); + T2 ("xxxxxxx", uint128_t, 0, 0, x00000001, == 0); + + T2 ("xxxxxxx", uint128_t, 0, x1, 0, == 0); + T2 ("xxxxxxx", uint128_t, 0, x01, 0, == 0); + T2 ("xxxxxxx", uint128_t, 0, x001, 0, == 0); + T2 ("xxxxxxx", uint128_t, 0, x0001, 0, == 0); + T2 ("xxxxxxx", uint128_t, 0, x00001, 0, == 0); + T2 ("xxxxxxx", uint128_t, 0, x000001, 0, == 0); + T2 ("xxxxxxx", uint128_t, 0, x0000001, 0, == 0); + T2 ("xxxxxxx", uint128_t, 0, x00000001, 0, == 0); + + T2 ("xxxxxxx", uint128_t, 0, 0, I64 ("\2\1\0\0\0\0\0\0"), == 2); + T2 ("xxxxxxx", uint128_t, 0, 0, I64 ("\0\2\1\0\0\0\0\0"), == 0); + + T2 ("xxxxxxx", uint128_t, 0, 0, I64 ("\3\2\1\0\0\0\0\0"), == 3); + T2 ("xxxxxxx", uint128_t, 0, 0, I64 ("\0\3\2\1\0\0\0\0"), == 0); + + uint64_t x4321 = I64 ("\4\3\2\1\0\0\0\0"); + uint64_t x54321 = I64 ("\5\4\3\2\1\0\0\0"); + uint64_t x654321 = I64 ("\6\5\4\3\2\1\0\0"); + uint64_t x7654321 = I64 ("\7\6\5\4\3\2\1\0"); + uint64_t x87654321 = I64 ("8\7\6\5\4\3\2\1"); + uint64_t x9 = I64 ("9\0\0\0\0\0\0\0"); + uint64_t xa9 = I64 ("a9\0\0\0\0\0\0"); + uint64_t xba9 = I64 ("ba9\0\0\0\0\0\0"); + uint64_t xcba9 = I64 ("cba9\0\0\0\0\0"); + uint64_t xdcba9 = I64 ("dcba9\0\0\0\0"); + uint64_t xedcba9 = I64 ("edcba9\0\0\0\0"); + uint64_t xfedcba9 = I64 ("fedcba9\0\0\0"); + + T2 (0, uint128_t, 0, 0, x4321, == 4); + T2 (0, uint128_t, 0, 0, x54321, == 5); + T2 (0, uint128_t, 0, 0, x654321, == 6); + T2 (0, uint128_t, 0, 0, x7654321, == 7); + T2 (0, uint128_t, 0, 0, x87654321, == 8); + T2 (0, uint128_t, 0, x9, x87654321, == 9); + T2 (0, uint128_t, 0, xa9, x87654321, == 10); + T2 (0, uint128_t, 0, xba9, x87654321, == 11); + T2 (0, uint128_t, 0, xcba9, x87654321, == 12); + T2 (0, uint128_t, 0, xdcba9, x87654321, == 13); + T2 (0, uint128_t, 0, xedcba9, x87654321, == 14); + T2 (0, uint128_t, 0, xfedcba9, x87654321, == 15); +} + +#endif // __uint128_t + +/* { dg-final { scan-tree-dump-times "strlen" 0 "optimized" } } + { dg-final { scan-tree-dump-times "_not_eliminated_" 0 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/strlenopt-71.c b/gcc/testsuite/gcc.dg/strlenopt-71.c new file mode 100644 index 0000000..1905519 --- /dev/null +++ b/gcc/testsuite/gcc.dg/strlenopt-71.c @@ -0,0 +1,212 @@ +/* PR tree-optimization/91183 - strlen of a strcpy result with a conditional + source not folded + Runtime test to verify that multibyte stores are handled correctly. + { dg-do run } + { dg-options "-O2 -Wall" } */ + +#include "strlenopt.h" + +#define CHAR_BIT __CHAR_BIT__ + +typedef __INT16_TYPE__ int16_t; +typedef __INT32_TYPE__ int32_t; + +#define NOIPA __attribute__ ((noclone, noinline, noipa)) + +/* Prevent the optimizer from detemining invariants from prior tests. */ +NOIPA void terminate (void) +{ + __builtin_abort (); +} + +#define VERIFY(expr, str) \ + do { \ + const unsigned expect = strlen (str); \ + const unsigned len = strlen (expr); \ + if (len != expect) \ + { \ + __builtin_printf ("line %i: strlen(%s) == %u failed: " \ + "got %u with a = \"%.*s\"\n", \ + __LINE__, #expr, expect, len, \ + (int)sizeof a, a); \ + terminate (); \ + } \ + if (memcmp (a, str, expect + 1)) \ + { \ + __builtin_printf ("line %i: expected string \"%s\", " \ + "got a = \"%.*s\"\n", \ + __LINE__, str, (int)sizeof a, a); \ + terminate (); \ + } \ + } while (0) + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define I16(s) ((s[0] << 8) + s[1]) +# define I32(s) ((s[0] << 24) + (s[1] << 16) + (s[2] << 8) + s[3]) +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define I16(s) ((s[1] << 8) + s[0]) +# define I32(s) ((s[3] << 24) + (s[2] << 16) + (s[1] << 8) + s[0]) +#endif + +char a[32]; + +NOIPA void +i16_1 (void) +{ + *(int16_t*)a = I16 ("12"); + *(int16_t*)(a + 2) = I16 ("3"); + VERIFY (a, "123"); + + *(int16_t*)(a + 1) = I16 ("23"); + VERIFY (a, "123"); + + *(int16_t*)(a) = I16 ("12"); + VERIFY (a, "123"); + + *(int16_t*)(a + 1) = I16 ("2"); + VERIFY (a, "12"); + + *(int16_t*)(a + 3) = I16 ("45"); + *(int16_t*)(a + 2) = I16 ("34"); + VERIFY (a, "12345"); +} + +NOIPA void +i16_2 (void) +{ + strcpy (a, "12"); + strcat (a, "34"); + + *(int16_t*)a = I16 ("12"); + VERIFY (a, "1234"); + + *(int16_t*)(a + 1) = I16 ("12"); + VERIFY (a, "1124"); + + *(int16_t*)(a + 2) = I16 ("12"); + VERIFY (a, "1112"); + + *(int16_t*)(a + 3) = I16 ("12"); + VERIFY (a, "11112"); + + *(int16_t*)(a + 4) = I16 ("12"); + VERIFY (a, "111112"); +} + + +NOIPA void +i32_1 (void) +{ + *(int32_t*)a = I32 ("1234"); + VERIFY (a, "1234"); + + *(int32_t*)(a + 1) = I32 ("2345"); + VERIFY (a, "12345"); +} + +NOIPA void +i32_2 (void) +{ + strcpy (a, "12"); + strcat (a, "34"); + + *(int32_t*)a = I32 ("1234"); + VERIFY (a, "1234"); + + *(int32_t*)(a + 4) = I32 ("567"); + VERIFY (a, "1234567"); + + *(int32_t*)(a + 7) = I32 ("89\0"); + VERIFY (a, "123456789"); + + *(int32_t*)(a + 3) = I32 ("4567"); + VERIFY (a, "123456789"); + + *(int32_t*)(a + 2) = I32 ("3456"); + VERIFY (a, "123456789"); + + *(int32_t*)(a + 1) = I32 ("2345"); + VERIFY (a, "123456789"); +} + + +NOIPA void +i32_3 (void) +{ + strcpy (a, "1234"); + strcat (a, "5678"); + + *(int32_t*)a = I32 ("1234"); + VERIFY (a, "12345678"); + + *(int32_t*)(a + 1) = I32 ("234"); + VERIFY (a, "1234"); + + *(int32_t*)(a + 2) = I32 ("3456"); + VERIFY (a, "12345678"); + + *(int32_t*)(a + 3) = I32 ("4567"); + VERIFY (a, "12345678"); + + *(int32_t*)(a + 4) = I32 ("5678"); + VERIFY (a, "12345678"); + + *(int32_t*)(a + 5) = I32 ("6789"); + VERIFY (a, "123456789"); + + *(int32_t*)(a + 6) = I32 ("789A"); + VERIFY (a, "123456789A"); +} + +volatile int vzero = 0; + +NOIPA void +i32_4 (void) +{ + strcpy (a, "1234"); + strcat (a, "5678"); + + *(int32_t*)a = vzero ? I32 ("1\0\0\0") : I32 ("1234"); + VERIFY (a, "12345678"); + + *(int32_t*)a = vzero ? I32 ("12\0\0") : I32 ("1234"); + VERIFY (a, "12345678"); + + *(int32_t*)a = vzero ? I32 ("123\0") : I32 ("1234"); + VERIFY (a, "12345678"); + + *(int32_t*)a = vzero ? I32 ("1234") : I32 ("1234"); + VERIFY (a, "12345678"); + + *(int32_t*)a = vzero ? I32 ("1235") : I32 ("1234"); + VERIFY (a, "12345678"); + + *(int32_t*)a = vzero ? I32 ("1234") : I32 ("123\0"); + VERIFY (a, "123"); + + *(int32_t*)(a + 3) = vzero ? I32 ("456\0") : I32 ("4567"); + VERIFY (a, "12345678"); +} + + +int main () +{ + memset (a, 0, sizeof a); + i16_1 (); + + memset (a, 0, sizeof a); + i16_2 (); + + + memset (a, 0, sizeof a); + i32_1 (); + + memset (a, 0, sizeof a); + i32_2 (); + + memset (a, 0, sizeof a); + i32_3 (); + + memset (a, 0, sizeof a); + i32_4 (); +} diff --git a/gcc/testsuite/gcc.dg/strlenopt-72.c b/gcc/testsuite/gcc.dg/strlenopt-72.c new file mode 100644 index 0000000..a06cc4f --- /dev/null +++ b/gcc/testsuite/gcc.dg/strlenopt-72.c @@ -0,0 +1,64 @@ +/* PR tree-optimization/91183 - strlen of a strcpy result with a conditional + source not folded + Test to verify that strlen can determine string lengths from wider stores + than narrow characters. This matters because on targets that can handle + unaligned stores and where GCC lowers multi-character stores into smaller + numbers of wider stores. + { dg-do compile } + { dg-options "-O2 -fdump-tree-optimized" } */ + +#include "strlenopt.h" + +#define CAT(x, y) x ## y +#define CONCAT(x, y) CAT (x, y) +#define FAILNAME(name) CONCAT (call_ ## name ##_on_line_, __LINE__) + +#define FAIL(name) do { \ + extern void FAILNAME (name) (void); \ + FAILNAME (name)(); \ + } while (0) + +/* Macros to emit a call to function named + call_failed_to_be_eliminated_on_line_NNN() + for each call that's expected to be eliminated. The dg-final + scan-tree-dump-time directive at the bottom of the test verifies + that no such call appears in output. */ +#define ELIM(expr) \ + if ((expr)) FAIL (not_eliminated); else (void)0 + +#undef T +#define T(N, ncpy, expect, assign) do { \ + char a[N], b[N]; \ + assign; \ + memcpy (b, a, ncpy); \ + ELIM (!(expect == strlen (b))); \ + } while (0) + +void test_copy (void) +{ + T (2, 1, 0, (a[0] = 0)); + T (2, 2, 0, (a[0] = 0, a[1] = 0)); + T (2, 2, 1, (a[0] = '1', a[1] = 0)); + T (4, 3, 2, (a[0] = '1', a[1] = '2', a[2] = 0)); + // Not handled due to pr83821: + // T (4, 3, 1, (a[0] = '1', a[1] = 0, a[2] = '2')); + T (4, 2, 1, (a[0] = '1', a[1] = 0, a[2] = 0, a[3] = 0)); + // Not handled due to pr83821: + // T (4, 3, 1, (a[0] = '1', a[1] = 0, a[2] = 0, a[3] = 0)); + T (4, 4, 1, (a[0] = '1', a[1] = 0, a[2] = 0, a[3] = 0)); + T (4, 3, 2, (a[0] = '1', a[1] = '2', a[2] = 0, a[3] = 0)); + T (4, 4, 2, (a[0] = '1', a[1] = '2', a[2] = 0, a[3] = 0)); + T (4, 4, 3, (a[0] = '1', a[1] = '2', a[2] = '3', a[3] = 0)); + T (5, 4, 1, (a[0] = '1', a[1] = 0, a[2] = 0, a[3] = 0)); + T (5, 4, 2, (a[0] = '1', a[1] = '2', a[2] = 0, a[3] = 0)); + T (5, 4, 3, (a[0] = '1', a[1] = '2', a[2] = '3', a[3] = 0)); + // Not handled: + // T (5, 5, 1, (a[0] = '1', a[1] = 0, a[2] = 0, a[3] = 0, a[4] = 0)); + // T (5, 5, 2, (a[0] = '1', a[1] = '2', a[2] = 0, a[3] = 0, a[4] = 0)); + // T (5, 5, 3, (a[0] = '1', a[1] = '2', a[2] = '3', a[3] = 0, a[4] = 0)); + T (5, 5, 4, (a[0] = '1', a[1] = '2', a[2] = '3', a[3] = '4', a[4] = 0)); +} + + +/* { dg-final { scan-tree-dump-times "strlen" 0 "optimized" } } + { dg-final { scan-tree-dump-times "_not_eliminated_" 0 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/strlenopt-8.c b/gcc/testsuite/gcc.dg/strlenopt-8.c index 85c6d38..f43b809 100644 --- a/gcc/testsuite/gcc.dg/strlenopt-8.c +++ b/gcc/testsuite/gcc.dg/strlenopt-8.c @@ -43,13 +43,7 @@ main () return 0; } -/* On non-strict-align targets we inline the memcpy that strcat is turned - into and end up with a short typed load / store which strlenopt is not - able to analyze. */ - -/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" { xfail non_strict_align } } } */ -/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen" { target { non_strict_align } } } } */ -/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" { target { ! non_strict_align } } } } */ +/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */ /* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */ /* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */ /* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */ diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index 88b6bd7..4af4785 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -3328,78 +3328,287 @@ handle_pointer_plus (gimple_stmt_iterator *gsi) } } -/* If RHS, either directly or indirectly, refers to a string of constant - length, return the length. Otherwise, if it refers to a character - constant, return 1 if the constant is non-zero and 0 if it is nul. - Otherwise, return a negative value. */ +/* Describes recursion limits used by count_nonzero_bytes. */ -static HOST_WIDE_INT -get_min_string_length (tree rhs, bool *full_string_p) +class ssa_name_limit_t { - if (INTEGRAL_TYPE_P (TREE_TYPE (rhs))) + bitmap visited; /* Bitmap of visited SSA_NAMEs. */ + unsigned ssa_def_max; /* Longest chain of SSA_NAMEs to follow. */ + + /* Not copyable or assignable. */ + ssa_name_limit_t (ssa_name_limit_t&); + void operator= (ssa_name_limit_t&); + + public: + + ssa_name_limit_t () + : visited (NULL), + ssa_def_max (PARAM_VALUE (PARAM_SSA_NAME_DEF_CHAIN_LIMIT)) { } + + int next_ssa_name (tree); + + ~ssa_name_limit_t () { - if (tree_expr_nonzero_p (rhs)) + if (visited) + BITMAP_FREE (visited); + } +}; + +/* If the SSA_NAME has already been "seen" return a positive value. + Otherwise add it to VISITED. If the SSA_NAME limit has been + reached, return a negative value. Otherwise return zero. */ + +int ssa_name_limit_t::next_ssa_name (tree ssa_name) +{ + if (!visited) + visited = BITMAP_ALLOC (NULL); + + /* Return a positive value if SSA_NAME has already been visited. */ + if (!bitmap_set_bit (visited, SSA_NAME_VERSION (ssa_name))) + return 1; + + /* Return a negative value to let caller avoid recursing beyond + the specified limit. */ + if (ssa_def_max == 0) + return -1; + + --ssa_def_max; + + return 0; +} + +/* Determine the minimum and maximum number of leading non-zero bytes + in the representation of EXP and set LENRANGE[0] and LENRANGE[1] + to each. Set LENRANGE[2] to the total number of bytes in + the representation. Set *NULTREM if the representation contains + a zero byte, and set *ALLNUL if all the bytes are zero. Avoid + recursing deeper than the limits in SNLIM allow. Return true + on success and false otherwise. */ + +static bool +count_nonzero_bytes (tree exp, unsigned lenrange[3], bool *nulterm, + bool *allnul, bool *allnonnul, ssa_name_limit_t &snlim) +{ + if (TREE_CODE (exp) == SSA_NAME) + { + /* Handle a single-character specially. */ + tree type = TREE_TYPE (exp); + if (TREE_CODE (type) == INTEGER_TYPE + && TYPE_MODE (type) == TYPE_MODE (char_type_node) + && TYPE_PRECISION (type) == TYPE_PRECISION (char_type_node)) { - *full_string_p = false; - return 1; + /* Determine if the character EXP is known to be non-zero + (even if its exact value is not known) and if so, recurse + once to set the range, etc. */ + if (tree_expr_nonzero_p (exp)) + return count_nonzero_bytes (build_int_cst (type, 1), lenrange, + nulterm, allnul, allnonnul, snlim); + /* Don't know whether EXP is or isn't nonzero. */ + return false; } - *full_string_p = true; - return 0; + gimple *stmt = SSA_NAME_DEF_STMT (exp); + if (gimple_code (stmt) != GIMPLE_PHI) + return false; + + /* Avoid processing an SSA_NAME that has already been visited + or if an SSA_NAME limit has been reached. Indicate success + if the former and failure if the latter. */ + if (int res = snlim.next_ssa_name (exp)) + return res > 0; + + /* Determine the minimum and maximum from the PHI arguments. */ + unsigned int n = gimple_phi_num_args (stmt); + for (unsigned i = 0; i != n; i++) + { + tree def = gimple_phi_arg_def (stmt, i); + if (!count_nonzero_bytes (def, lenrange, nulterm, allnul, allnonnul, + snlim)) + return false; + } + + return true; } - if (TREE_CODE (rhs) == MEM_REF - && integer_zerop (TREE_OPERAND (rhs, 1))) + /* Offset from the beginning of the representation bytes, a pointer + to the representation, and the number of bytes of the representation + to consider (may be less than the object size for MEM_REF). */ + unsigned HOST_WIDE_INT offset = 0; + const char *prep = NULL; + unsigned nbytes = 0; + + if (TREE_CODE (exp) == MEM_REF) { - rhs = TREE_OPERAND (rhs, 0); - if (TREE_CODE (rhs) == ADDR_EXPR) - { - tree rhs_addr = rhs; + /* If the MEM_REF operand is the address of an object such as + a string or integer, extract it and the offset into it. */ + tree arg = TREE_OPERAND (exp, 0); + if (TREE_CODE (arg) != ADDR_EXPR) + return false; + + tree off = TREE_OPERAND (exp, 1); + if (TREE_CODE (off) != INTEGER_CST + || !tree_fits_uhwi_p (off)) + return false; - rhs = TREE_OPERAND (rhs, 0); - if (TREE_CODE (rhs) != STRING_CST) + offset = tree_to_uhwi (off); + if (INT_MAX < offset) + return false; + + /* The size of the MEM_REF access determines the number of bytes. */ + tree type = TREE_TYPE (exp); + if (tree typesize = TYPE_SIZE_UNIT (type)) + nbytes = tree_to_uhwi (typesize); + + if (offset == 0 && TREE_CODE (exp) != STRING_CST) + { + int idx = get_stridx (arg); + if (idx > 0) { - int idx = get_stridx (rhs_addr); - if (idx > 0) + strinfo *si = get_strinfo (idx); + if (si && tree_fits_shwi_p (si->nonzero_chars)) { - strinfo *si = get_strinfo (idx); - if (si - && tree_fits_shwi_p (si->nonzero_chars)) - { - *full_string_p = si->full_string_p; - return tree_to_shwi (si->nonzero_chars); - } + unsigned len = tree_to_shwi (si->nonzero_chars); + if (len < lenrange[0]) + lenrange[0] = len; + if (lenrange[1] < len) + lenrange[1] = len; + + if (!si->full_string_p) + *nulterm = false; + + /* Since only the length of the string are known and + its contents, clear ALLNUL and ALLNONNUL purely on + the basis of the length. */ + if (len) + *allnul = false; + else + *allnonnul = false; + return true; } } } + + /* Proceed to extract the object representation below. */ + exp = TREE_OPERAND (arg, 0); } - if (TREE_CODE (rhs) == VAR_DECL - && TREE_READONLY (rhs)) - rhs = DECL_INITIAL (rhs); + if (TREE_CODE (exp) == VAR_DECL && TREE_READONLY (exp)) + { + exp = DECL_INITIAL (exp); + if (!exp) + return false; + } - if (rhs && TREE_CODE (rhs) == STRING_CST) + if (TREE_CODE (exp) == STRING_CST) { - HOST_WIDE_INT len = strlen (TREE_STRING_POINTER (rhs)); - *full_string_p = len < TREE_STRING_LENGTH (rhs); - return len; + /* Set PREP and NBYTES to the string representation. */ + gcc_assert (offset <= INT_MAX); + + if (!nbytes) + { + /* Unless NBYTES has already been determined above from + MEM_REF, set it here. It includes all internal nuls, + including the terminating one if the string has one. */ + nbytes = TREE_STRING_LENGTH (exp); + if (nbytes <= offset) + return false; + } + + prep = TREE_STRING_POINTER (exp) + offset; } - return -1; + unsigned char buf[256]; + if (!prep) + { + /* Try to extract the representation of the constant object. */ + nbytes = native_encode_expr (exp, buf, sizeof buf, -1); + if (!nbytes) + return false; + + prep = reinterpret_cast (buf); + } + + /* Compute the number of leading nonzero bytes in the representation + and update the minimum and maximum. */ + unsigned n = strnlen (prep, nbytes); + + if (n < lenrange[0]) + lenrange[0] = n; + if (lenrange[1] < n) + lenrange[1] = n; + + /* Set the size of the representation. */ + if (lenrange[2] < nbytes) + lenrange[2] = nbytes; + + /* Clear NULTERM if none of the bytes is zero. */ + if (n == nbytes) + *nulterm = false; + + if (n) + { + /* When the initial number of non-zero bytes N is non-zero, reset + *ALLNUL; if N is less than that the size of the representation + also clear *ALLNONNUL. */ + *allnul = false; + if (n < nbytes) + *allnonnul = false; + } + else if (*allnul || *allnonnul) + { + *allnonnul = false; + + if (*allnul) + { + /* When either ALLNUL is set and N is zero, also determine + whether all subsequent bytes after the first one (which + is nul) are zero or nonzero and clear ALLNUL if not. */ + for (const char *p = prep; p != prep + nbytes; ++p) + if (*p) + { + *allnul = false; + break; + } + } + } + + return true; +} + +/* Same as above except with an implicit SSA_NAME limit. */ + +static bool +count_nonzero_bytes (tree exp, unsigned lenrange[3], bool *nulterm, + bool *allnul, bool *allnonnul) +{ + /* Set to optimistic values so the caller doesn't have to worry about + initializing these and to what. On success, the function will clear + these if it determines their values are different but being recursive + it never sets either to true. On failure, their values are + unspecified. */ + *nulterm = true; + *allnul = true; + *allnonnul = true; + + ssa_name_limit_t snlim; + return count_nonzero_bytes (exp, lenrange, nulterm, allnul, allnonnul, snlim); } -/* Handle a single or multiple character store either by single - character assignment or by multi-character assignment from - MEM_REF. */ +/* Handle a single or multibyte store other than by a built-in function, + either via a single character assignment or by multi-byte assignment + either via MEM_REF or via a type other than char (such as in + '*(int*)a = 12345'). Return true when handled. */ static bool -handle_char_store (gimple_stmt_iterator *gsi) +handle_store (gimple_stmt_iterator *gsi) { int idx = -1; strinfo *si = NULL; gimple *stmt = gsi_stmt (*gsi); tree ssaname = NULL_TREE, lhs = gimple_assign_lhs (stmt); tree rhs = gimple_assign_rhs1 (stmt); + + /* The offset of the first byte in LHS modified by the the store. */ unsigned HOST_WIDE_INT offset = 0; if (TREE_CODE (lhs) == MEM_REF @@ -3428,23 +3637,77 @@ handle_char_store (gimple_stmt_iterator *gsi) si = get_strinfo (idx); } + /* Minimum and maximum leading non-zero bytes and the size of the store. */ + unsigned lenrange[] = { UINT_MAX, 0, 0 }; + + /* Set to the minimum length of the string being assigned if known. */ + unsigned HOST_WIDE_INT rhs_minlen; + /* STORING_NONZERO_P is true iff not all stored characters are zero. + STORING_ALL_NONZERO_P is true if all stored characters are zero. STORING_ALL_ZEROS_P is true iff all stored characters are zero. Both are false when it's impossible to determine which is true. */ bool storing_nonzero_p; - bool storing_all_zeros_p = initializer_zerop (rhs, &storing_nonzero_p); - if (!storing_nonzero_p) - storing_nonzero_p = tree_expr_nonzero_p (rhs); - bool full_string_p = storing_all_zeros_p; + bool storing_all_nonzero_p; + bool storing_all_zeros_p; + /* FULL_STRING_P is set when the stored sequence of characters form + a nul-terminated string. */ + bool full_string_p; - /* Set to the length of the string being assigned if known. */ - HOST_WIDE_INT rhslen; + const bool ranges_valid + = count_nonzero_bytes (rhs, lenrange, &full_string_p, + &storing_all_zeros_p, &storing_all_nonzero_p); + if (ranges_valid) + { + rhs_minlen = lenrange[0]; + storing_nonzero_p = lenrange[1] > 0; + + if (tree dstsize = compute_objsize (lhs, 1)) + if (compare_tree_int (dstsize, lenrange[2]) < 0) + warning_n (gimple_location (stmt), OPT_Wstringop_overflow_, + lenrange[2], + "%Gwriting %u byte into a region of size %E", + "%Gwriting %u bytes into a region of size %E", + stmt, lenrange[2], dstsize); + } + else + { + rhs_minlen = HOST_WIDE_INT_M1U; + full_string_p = false; + storing_nonzero_p = false; + storing_all_zeros_p = false; + storing_all_nonzero_p = false; + } if (si != NULL) { - int cmp = compare_nonzero_chars (si, offset); - gcc_assert (offset == 0 || cmp >= 0); - if (storing_all_zeros_p && cmp == 0 && si->full_string_p) + /* The corresponding element is set to 1 if the first and last + element, respectively, of the sequence of characters being + written over the string described by SI ends before + the terminating nul (if it has one), to zero if the nul is + being overwritten but not beyond, or negative otherwise. */ + int store_before_nul[2]; + if (ranges_valid) + { + /* The offset of the last stored byte. */ + unsigned HOST_WIDE_INT endoff = offset + lenrange[2] - 1; + store_before_nul[0] = compare_nonzero_chars (si, offset); + if (endoff == offset) + store_before_nul[1] = store_before_nul[0]; + else + store_before_nul[1] = compare_nonzero_chars (si, endoff); + } + else + { + store_before_nul[0] = compare_nonzero_chars (si, offset); + store_before_nul[1] = store_before_nul[0]; + gcc_assert (offset == 0 || store_before_nul[0] >= 0); + } + + if (storing_all_zeros_p + && store_before_nul[0] == 0 + && store_before_nul[1] == 0 + && si->full_string_p) { /* When overwriting a '\0' with a '\0', the store can be removed if we know it has been stored in the current function. */ @@ -3463,16 +3726,21 @@ handle_char_store (gimple_stmt_iterator *gsi) } } - if (cmp > 0 + if (store_before_nul[1] > 0 && storing_nonzero_p + && lenrange[0] == lenrange[1] + && lenrange[0] == lenrange[2] && TREE_CODE (TREE_TYPE (rhs)) == INTEGER_TYPE) { - /* Handle a single non-nul character store. + /* Handle a store of one or more non-nul characters that ends + before the terminating nul of the destination and so does + not affect its length If si->nonzero_chars > OFFSET, we aren't overwriting '\0', - and if we aren't storing '\0', we know that the length of the - string and any other zero terminated string in memory remains - the same. In that case we move to the next gimple statement and - return to signal the caller that it shouldn't invalidate anything. + and if we aren't storing '\0', we know that the length of + the string and any other zero terminated string in memory + remains the same. In that case we move to the next gimple + statement and return to signal the caller that it shouldn't + invalidate anything. This is benefical for cases like: @@ -3493,7 +3761,7 @@ handle_char_store (gimple_stmt_iterator *gsi) if (storing_all_zeros_p || storing_nonzero_p - || (offset != 0 && cmp > 0)) + || (offset != 0 && store_before_nul[1] > 0)) { /* When STORING_NONZERO_P, we know that the string will start with at least OFFSET + 1 nonzero characters. If storing @@ -3506,22 +3774,15 @@ handle_char_store (gimple_stmt_iterator *gsi) OFFSET characters long. Otherwise, we're storing an unknown value at offset OFFSET, - so need to clip the nonzero_chars to OFFSET. */ - bool full_string_p = storing_all_zeros_p; - HOST_WIDE_INT len = 1; - if (storing_nonzero_p) - { - /* Try to get the minimum length of the string (or - individual character) being stored. If it fails, - STORING_NONZERO_P guarantees it's at least 1. */ - len = get_min_string_length (rhs, &full_string_p); - if (len < 0) - len = 1; - } - + so need to clip the nonzero_chars to OFFSET. + Use the minimum length of the string (or individual character) + being stored if it's known. Otherwise, STORING_NONZERO_P + guarantees it's at least 1. */ + HOST_WIDE_INT len + = storing_nonzero_p && ranges_valid ? lenrange[0] : 1; location_t loc = gimple_location (stmt); tree oldlen = si->nonzero_chars; - if (cmp == 0 && si->full_string_p) + if (store_before_nul[1] == 0 && si->full_string_p) /* We're overwriting the nul terminator with a nonzero or unknown character. If the previous stmt was a memcpy, its length may be decreased. */ @@ -3567,8 +3828,7 @@ handle_char_store (gimple_stmt_iterator *gsi) HOST_WIDE_INT slen = (storing_all_zeros_p ? 0 : (storing_nonzero_p - ? get_min_string_length (rhs, &full_string_p) - : -1)); + && ranges_valid ? lenrange[0] : -1)); tree len = (slen <= 0 ? size_zero_node : build_int_cst (size_type_node, slen)); @@ -3583,18 +3843,18 @@ handle_char_store (gimple_stmt_iterator *gsi) } } else if (idx == 0 - && (rhslen = get_min_string_length (rhs, &full_string_p)) >= 0 + && rhs_minlen < HOST_WIDE_INT_M1U && ssaname == NULL_TREE && TREE_CODE (TREE_TYPE (lhs)) == ARRAY_TYPE) { HOST_WIDE_INT a = int_size_in_bytes (TREE_TYPE (lhs)); - if (a > 0 && (unsigned HOST_WIDE_INT) a > (unsigned HOST_WIDE_INT) rhslen) + if (a > 0 && (unsigned HOST_WIDE_INT) a > rhs_minlen) { int idx = new_addr_stridx (lhs); if (idx != 0) { si = new_strinfo (build_fold_addr_expr (lhs), idx, - build_int_cst (size_type_node, rhslen), + build_int_cst (size_type_node, rhs_minlen), full_string_p); set_strinfo (idx, si); si->dont_invalidate = true; @@ -3707,6 +3967,16 @@ fold_strstr_to_strncmp (tree rhs1, tree rhs2, gimple *stmt) } } +/* Return true if TYPE corresponds to a narrow character type. */ + +static bool +is_char_type (tree type) +{ + return (TREE_CODE (type) == INTEGER_TYPE + && TYPE_MODE (type) == TYPE_MODE (char_type_node) + && TYPE_PRECISION (type) == TYPE_PRECISION (char_type_node)); +} + /* Attempt to check for validity of the performed access a single statement at *GSI using string length knowledge, and to optimize it. If the given basic block needs clean-up of EH, CLEANUP_EH is set to @@ -3907,18 +4177,32 @@ strlen_check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh) } } 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); - if (TREE_CODE (type) == INTEGER_TYPE - && TYPE_MODE (type) == TYPE_MODE (char_type_node) - && TYPE_PRECISION (type) == TYPE_PRECISION (char_type_node)) - { - if (! handle_char_store (gsi)) - return false; - } - } + { + 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; + } + + /* Handle a single or multibyte assignment. */ + if (is_char_store && !handle_store (gsi)) + return false; + } } else if (gcond *cond = dyn_cast (stmt)) { -- 2.7.4