From a7160771da8b77a03317aab2c27706ba70fe3e9c Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Wed, 9 Oct 2019 21:35:11 +0000 Subject: [PATCH] PR tree-optimization/90879 - fold zero-equality of strcmp between a longer string and a smaller array gcc/c-family/ChangeLog: PR tree-optimization/90879 * c.opt (-Wstring-compare): New option. gcc/testsuite/ChangeLog: PR tree-optimization/90879 * gcc.dg/Wstring-compare-2.c: New test. * gcc.dg/Wstring-compare.c: New test. * gcc.dg/strcmpopt_3.c: Scan the optmized dump instead of strlen. * gcc.dg/strcmpopt_6.c: New test. * gcc.dg/strlenopt-65.c: Remove uinnecessary declarations, add test cases. * gcc.dg/strlenopt-66.c: Run it. * gcc.dg/strlenopt-68.c: New test. gcc/ChangeLog: PR tree-optimization/90879 * builtins.c (check_access): Avoid using maxbound when null. * calls.c (maybe_warn_nonstring_arg): Adjust to get_range_strlen change. * doc/invoke.texi (-Wstring-compare): Document new warning option. * gimple-fold.c (get_range_strlen_tree): Make setting maxbound conditional. (get_range_strlen): Overwrite initial maxbound when non-null. * gimple-ssa-sprintf.c (get_string_length): Adjust to get_range_strlen changes. * tree-ssa-strlen.c (maybe_diag_stxncpy_trunc): Same. (used_only_for_zero_equality): New function. (handle_builtin_memcmp): Call it. (determine_min_objsize): Return an integer instead of tree. (get_len_or_size, strxcmp_eqz_result): New functions. (maybe_warn_pointless_strcmp): New function. (handle_builtin_string_cmp): Call it. Fold zero-equality of strcmp between a longer string and a smaller array. (get_range_strlen_dynamic): Overwrite initial maxbound when non-null. From-SVN: r276773 --- gcc/ChangeLog | 21 + gcc/builtins.c | 2 +- gcc/c-family/ChangeLog | 5 + gcc/c-family/c.opt | 6 + gcc/calls.c | 8 + gcc/doc/invoke.texi | 26 ++ gcc/gimple-fold.c | 36 +- gcc/gimple-ssa-sprintf.c | 44 +- gcc/testsuite/ChangeLog | 12 + gcc/testsuite/gcc.dg/Wstring-compare-2.c | 127 ++++++ gcc/testsuite/gcc.dg/Wstring-compare.c | 181 +++++++++ gcc/testsuite/gcc.dg/strcmpopt_3.c | 20 +- gcc/testsuite/gcc.dg/strcmpopt_6.c | 207 ++++++++++ gcc/testsuite/gcc.dg/strlenopt-65.c | 53 ++- gcc/testsuite/gcc.dg/strlenopt-66.c | 38 +- gcc/testsuite/gcc.dg/strlenopt-69.c | 126 ++++++ gcc/tree-ssa-strlen.c | 664 +++++++++++++++++++------------ 17 files changed, 1261 insertions(+), 315 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/Wstring-compare-2.c create mode 100644 gcc/testsuite/gcc.dg/Wstring-compare.c create mode 100644 gcc/testsuite/gcc.dg/strcmpopt_6.c create mode 100644 gcc/testsuite/gcc.dg/strlenopt-69.c diff --git a/gcc/ChangeLog b/gcc/ChangeLog index ecbe825..ada9be7 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,24 @@ +2019-10-09 Martin Sebor + + PR tree-optimization/90879 + * builtins.c (check_access): Avoid using maxbound when null. + * calls.c (maybe_warn_nonstring_arg): Adjust to get_range_strlen change. + * doc/invoke.texi (-Wstring-compare): Document new warning option. + * gimple-fold.c (get_range_strlen_tree): Make setting maxbound + conditional. + (get_range_strlen): Overwrite initial maxbound when non-null. + * gimple-ssa-sprintf.c (get_string_length): Adjust to get_range_strlen + changes. + * tree-ssa-strlen.c (maybe_diag_stxncpy_trunc): Same. + (used_only_for_zero_equality): New function. + (handle_builtin_memcmp): Call it. + (determine_min_objsize): Return an integer instead of tree. + (get_len_or_size, strxcmp_eqz_result): New functions. + (maybe_warn_pointless_strcmp): New function. + (handle_builtin_string_cmp): Call it. Fold zero-equality of strcmp + between a longer string and a smaller array. + (get_range_strlen_dynamic): Overwrite initial maxbound when non-null. + 2019-10-09 Iain Sandoe * config/darwin.c (darwin_override_options): Make the check for diff --git a/gcc/builtins.c b/gcc/builtins.c index a1dc83c..5d811f1 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -3334,7 +3334,7 @@ check_access (tree exp, tree, tree, tree dstwrite, c_strlen_data lendata = { }; get_range_strlen (srcstr, &lendata, /* eltsize = */ 1); range[0] = lendata.minlen; - range[1] = lendata.maxbound; + range[1] = lendata.maxbound ? lendata.maxbound : lendata.maxlen; if (range[0] && (!maxread || TREE_CODE (maxread) == INTEGER_CST)) { if (maxread && tree_int_cst_le (maxread, range[0])) diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog index 3012f8b..398ba6d 100644 --- a/gcc/c-family/ChangeLog +++ b/gcc/c-family/ChangeLog @@ -1,3 +1,8 @@ +2019-10-09 Martin Sebor + + PR tree-optimization/90879 + * c.opt (-Wstring-compare): New option. + 2019-10-08 Andrew Sutton Jason Merrill diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 8f6867b..a1360ba 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -795,6 +795,12 @@ Wsizeof-array-argument C ObjC C++ ObjC++ Var(warn_sizeof_array_argument) Warning Init(1) Warn when sizeof is applied on a parameter declared as an array. +Wstring-compare +C ObjC C++ LTO ObjC++ Warning Var(warn_string_compare) Warning LangEnabledBy(C ObjC C++ ObjC++, Wextra) +Warn about calls to strcmp and strncmp used in equality expressions that +are necessarily true or false due to the length of one and size of the other +argument. + Wstringop-overflow C ObjC C++ LTO ObjC++ Warning Alias(Wstringop-overflow=, 2, 0) Warn about buffer overflow in string manipulation functions like memcpy diff --git a/gcc/calls.c b/gcc/calls.c index 51ad55f..ae90447 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -1614,6 +1614,10 @@ maybe_warn_nonstring_arg (tree fndecl, tree exp) if (!get_attr_nonstring_decl (arg)) { c_strlen_data lendata = { }; + /* Set MAXBOUND to an arbitrary non-null non-integer + node as a request to have it set to the length of + the longest string in a PHI. */ + lendata.maxbound = arg; get_range_strlen (arg, &lendata, /* eltsize = */ 1); maxlen = lendata.maxbound; } @@ -1639,6 +1643,10 @@ maybe_warn_nonstring_arg (tree fndecl, tree exp) if (!get_attr_nonstring_decl (arg)) { c_strlen_data lendata = { }; + /* Set MAXBOUND to an arbitrary non-null non-integer + node as a request to have it set to the length of + the longest string in a PHI. */ + lendata.maxbound = arg; get_range_strlen (arg, &lendata, /* eltsize = */ 1); maxlen = lendata.maxbound; } diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index dc9a3e5..9247603 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -349,6 +349,7 @@ Objective-C and Objective-C++ Dialects}. -Wsizeof-pointer-memaccess -Wsizeof-array-argument @gol -Wstack-protector -Wstack-usage=@var{byte-size} -Wstrict-aliasing @gol -Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol +-Wstring-compare @gol -Wstringop-overflow=@var{n} -Wstringop-truncation -Wsubobject-linkage @gol -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{|}malloc@r{]} @gol -Wsuggest-final-types @gol -Wsuggest-final-methods -Wsuggest-override @gol @@ -4485,6 +4486,7 @@ name is still supported, but the newer name is more descriptive.) -Wold-style-declaration @r{(C only)} @gol -Woverride-init @gol -Wsign-compare @r{(C only)} @gol +-Wstring-compare @gol -Wredundant-move @r{(only for C++)} @gol -Wtype-limits @gol -Wuninitialized @gol @@ -5800,6 +5802,30 @@ comparisons, so this warning level gives a very large number of false positives. @end table +@item -Wstring-compare +@opindex Wstring-compare +@opindex Wno-string-compare +Warn for calls to @code{strcmp} and @code{strncmp} whose result is +determined to be either zero or non-zero in tests for such equality +owing to the length of one argument being greater than the size of +the array the other argument is stored in (or the bound in the case +of @code{strncmp}). Such calls could be mistakes. For example, +the call to @code{strcmp} below is diagnosed because its result is +necessarily non-zero irrespective of the contents of the array @code{a}. + +@smallexample +extern char a[4]; +void f (char *d) +@{ + strcpy (d, "string"); + @dots{} + if (0 == strcmp (a, d)) // cannot be true + puts ("a and d are the same"); +@} +@end smallexample + +@option{-Wstring-compare} is enabled by @option{-Wextra}. + @item -Wstringop-overflow @itemx -Wstringop-overflow=@var{type} @opindex Wstringop-overflow diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c index 8d642de..a085ab2 100644 --- a/gcc/gimple-fold.c +++ b/gcc/gimple-fold.c @@ -1348,6 +1348,10 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind, } } + /* Set if VAL represents the maximum length based on array size (set + when exact length cannot be determined). */ + bool maxbound = false; + if (!val && rkind == SRK_LENRANGE) { if (TREE_CODE (arg) == ADDR_EXPR) @@ -1443,6 +1447,7 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind, pdata->minlen = ssize_int (0); } } + maxbound = true; } if (!val) @@ -1456,25 +1461,23 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind, && tree_int_cst_lt (val, pdata->minlen))) pdata->minlen = val; - if (pdata->maxbound) + if (pdata->maxbound && TREE_CODE (pdata->maxbound) == INTEGER_CST) { /* Adjust the tighter (more optimistic) string length bound if necessary and proceed to adjust the more conservative bound. */ if (TREE_CODE (val) == INTEGER_CST) { - if (TREE_CODE (pdata->maxbound) == INTEGER_CST) - { - if (tree_int_cst_lt (pdata->maxbound, val)) - pdata->maxbound = val; - } - else - pdata->maxbound = build_all_ones_cst (size_type_node); + if (tree_int_cst_lt (pdata->maxbound, val)) + pdata->maxbound = val; } else pdata->maxbound = val; } - else + else if (pdata->maxbound || maxbound) + /* Set PDATA->MAXBOUND only if it either isn't INTEGER_CST or + if VAL corresponds to the maximum length determined based + on the type of the object. */ pdata->maxbound = val; if (tight_bound) @@ -1655,8 +1658,11 @@ get_range_strlen (tree arg, bitmap *visited, /* Try to obtain the range of the lengths of the string(s) referenced by ARG, or the size of the largest array ARG refers to if the range - of lengths cannot be determined, and store all in *PDATA. ELTSIZE - is the expected size of the string element in bytes: 1 for char and + of lengths cannot be determined, and store all in *PDATA which must + be zero-initialized on input except PDATA->MAXBOUND may be set to + a non-null tree node other than INTEGER_CST to request to have it + set to the length of the longest string in a PHI. ELTSIZE is + the expected size of the string element in bytes: 1 for char and some power of 2 for wide characters. Return true if the range [PDATA->MINLEN, PDATA->MAXLEN] is suitable for optimization. Returning false means that a nonzero PDATA->MINLEN @@ -1668,6 +1674,7 @@ bool get_range_strlen (tree arg, c_strlen_data *pdata, unsigned eltsize) { bitmap visited = NULL; + tree maxbound = pdata->maxbound; if (!get_range_strlen (arg, &visited, SRK_LENRANGE, pdata, eltsize)) { @@ -1680,9 +1687,10 @@ get_range_strlen (tree arg, c_strlen_data *pdata, unsigned eltsize) else if (!pdata->minlen) pdata->minlen = ssize_int (0); - /* Unless its null, leave the more conservative MAXBOUND unchanged. */ - if (!pdata->maxbound) - pdata->maxbound = pdata->maxlen; + /* If it's unchanged from it initial non-null value, set the conservative + MAXBOUND to SIZE_MAX. Otherwise leave it null (if it is null). */ + if (maxbound && pdata->maxbound == maxbound) + pdata->maxbound = build_all_ones_cst (size_type_node); if (visited) BITMAP_FREE (visited); diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c index b11d798..b548bbd 100644 --- a/gcc/gimple-ssa-sprintf.c +++ b/gcc/gimple-ssa-sprintf.c @@ -1974,8 +1974,11 @@ get_string_length (tree str, unsigned eltsize, const vr_values *vr) if (!str) return fmtresult (); - /* Try to determine the dynamic string length first. */ + /* Try to determine the dynamic string length first. + Set MAXBOUND to an arbitrary non-null non-integer node as a request + to have it set to the length of the longest string in a PHI. */ c_strlen_data lendata = { }; + lendata.maxbound = str; if (eltsize == 1) get_range_strlen_dynamic (str, &lendata, vr); else @@ -1988,26 +1991,27 @@ get_string_length (tree str, unsigned eltsize, const vr_values *vr) get_range_strlen (str, &lendata, eltsize); } - /* LENDATA.MAXBOUND is null when LENDATA.MIN corresponds to the shortest - string referenced by STR. Otherwise, if it's not equal to .MINLEN it - corresponds to the bound of the largest array STR refers to, if known, - or it's SIZE_MAX otherwise. */ + /* If LENDATA.MAXBOUND is not equal to .MINLEN it corresponds to the bound + of the largest array STR refers to, if known, or it's set to SIZE_MAX + otherwise. */ /* Return the default result when nothing is known about the string. */ - if (lendata.maxbound) + if ((lendata.maxbound && !tree_fits_uhwi_p (lendata.maxbound)) + || !tree_fits_uhwi_p (lendata.maxlen)) { - if (integer_all_onesp (lendata.maxbound) - && integer_all_onesp (lendata.maxlen)) - return fmtresult (); - - if (!tree_fits_uhwi_p (lendata.maxbound) - || !tree_fits_uhwi_p (lendata.maxlen)) - return fmtresult (); - - unsigned HOST_WIDE_INT lenmax = tree_to_uhwi (max_object_size ()) - 2; - if (lenmax <= tree_to_uhwi (lendata.maxbound) - && lenmax <= tree_to_uhwi (lendata.maxlen)) - return fmtresult (); + fmtresult res; + res.nonstr = lendata.decl; + return res; + } + + unsigned HOST_WIDE_INT lenmax = tree_to_uhwi (max_object_size ()) - 2; + if (integer_zerop (lendata.minlen) + && (!lendata.maxbound || lenmax <= tree_to_uhwi (lendata.maxbound)) + && lenmax <= tree_to_uhwi (lendata.maxlen)) + { + fmtresult res; + res.nonstr = lendata.decl; + return res; } HOST_WIDE_INT min @@ -2056,9 +2060,9 @@ get_string_length (tree str, unsigned eltsize, const vr_values *vr) { /* When the upper bound is unknown (it can be zero or excessive) set the likely length to the greater of 1. If MAXBOUND is - set, also reset the length of the lower bound to zero. */ + known, also reset the length of the lower bound to zero. */ res.range.likely = res.range.min ? res.range.min : warn_level > 1; - if (lendata.maxbound) + if (lendata.maxbound && !integer_all_onesp (lendata.maxbound)) res.range.min = 0; } diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 5ee2a81..d03827d 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,15 @@ +2019-10-09 Martin Sebor + + PR tree-optimization/90879 + * gcc.dg/Wstring-compare-2.c: New test. + * gcc.dg/Wstring-compare.c: New test. + * gcc.dg/strcmpopt_3.c: Scan the optmized dump instead of strlen. + * gcc.dg/strcmpopt_6.c: New test. + * gcc.dg/strlenopt-65.c: Remove uinnecessary declarations, add + test cases. + * gcc.dg/strlenopt-66.c: Run it. + * gcc.dg/strlenopt-68.c: New test. + 2019-10-09 Marek Polacek PR c++/91364 - P0388R4: Permit conversions to arrays of unknown bound. diff --git a/gcc/testsuite/gcc.dg/Wstring-compare-2.c b/gcc/testsuite/gcc.dg/Wstring-compare-2.c new file mode 100644 index 0000000..e6ca2a6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstring-compare-2.c @@ -0,0 +1,127 @@ +/* PR tree-optimization/90879 - fold zero-equality of strcmp between + a longer string and a smaller array + Test for a warning for strcmp of a longer string against smaller + array. + { dg-do compile } + { dg-options "-O2 -Wall -Wstring-compare -Wno-stringop-truncation -ftrack-macro-expansion=0" } */ + +typedef __SIZE_TYPE__ size_t; + +extern void* memcpy (void*, const void*, size_t); + +extern int strcmp (const char*, const char*); +extern size_t strlen (const char*); +extern char* strcpy (char*, const char*); +extern char* strncpy (char*, const char*, size_t); +extern int strncmp (const char*, const char*, size_t); + +void sink (int, ...); +#define sink(...) sink (__LINE__, __VA_ARGS__) + + +extern char a1[1], a2[2], a3[3], a4[4], a5[5], a6[6], a7[7], a8[8], a9[9]; + +#define T(a, b) sink (0 == strcmp (a, b)) + + +void test_string_cst (void) +{ + const char *s1 = "1", *s2 = "12"; + + T (s1, a1); // { dg-warning ".strcmp. of a string of length 1 and an array of size 1 evaluates to nonzero" } + T (s1, a2); + T (s1, a3); + + T (a1, s1); // { dg-warning ".strcmp. of a string of length 1 and an array of size 1 evaluates to nonzero" } + T (a2, s1); + T (a3, s1); + + T (s2, a1); // { dg-warning ".strcmp. of a string of length 2 and an array of size 1 evaluates to nonzero" } + T (s2, a2); // { dg-warning ".strcmp. of a string of length 2 and an array of size 2 evaluates to nonzero" } + T (s2, a3); + + T (a1, s2); // { dg-warning ".strcmp. of a string of length 2 and an array of size 1 evaluates to nonzero" } + T (a2, s2); // { dg-warning ".strcmp. of a string of length 2 and an array of size 2 evaluates to nonzero" } + T (a3, s2); +} + + +void test_string_cst_off_cst (void) +{ + const char *s1 = "1", *s2 = "12", *s3 = "123", *s4 = "1234"; + + T (s1, a2 + 1); // { dg-warning ".strcmp. of a string of length 1 and an array of size 1 evaluates to nonzero" } + T (a2 + 1, s1); // { dg-warning ".strcmp. of a string of length 1 and an array of size 1 evaluates to nonzero" } + + + T (s3 + 1, a2); // { dg-warning ".strcmp. of a string of length 2 and an array of size 2 evaluates to nonzero" } + T (s3 + 1, a3); + + T (s2, a4 + 1); + T (s2, a4 + 2); // { dg-warning ".strcmp. of a string of length 2 and an array of size 2 evaluates to nonzero" } + + T (s4, a4 + 1); // { dg-warning ".strcmp. of a string of length 4 and an array of size 3 evaluates to nonzero" } + T (s3, a5 + 1); +} + + +/* Use strncpy below rather than memcpy until PR 91183 is resolved. */ + +#undef T +#define T(s, n, a) \ + do { \ + char arr[32]; \ + sink (arr); \ + strncpy (arr, s, n < 0 ? strlen (s) + 1: n); \ + sink (0 == strcmp (arr, a)); \ + } while (0) + +void test_string_exact_length (void) +{ + const char *s1 = "1", *s2 = "12"; + + T (s1, -1, a1); // { dg-warning ".strcmp. of a string of length 1 and an array of size 1 evaluates to nonzero" } + T (s1, -1, a2); + T (s1, -1, a3); + + T (s2, -1, a1); // { dg-warning ".strcmp. of a string of length 2 and an array of size 1 evaluates to nonzero" } + T (s2, -1, a2); // { dg-warning ".strcmp. of a string of length 2 and an array of size 2 evaluates to nonzero" } + T (s2, -1, a3); +} + + +void test_string_min_length (void) +{ + const char *s1 = "1", *s2 = "12"; + + T (s1, 1, a1); // { dg-warning ".strcmp. of a string of length 1 or more and an array of size 1 evaluates to nonzero" } + T (s1, 1, a2); + T (s1, 1, a3); + + T (s2, 2, a1); // { dg-warning ".strcmp. of a string of length 2 or more and an array of size 1 evaluates to nonzero" } + T (s2, 2, a2); // { dg-warning ".strcmp. of a string of length 2 or more and an array of size 2 evaluates to nonzero" } + T (s2, 2, a3); +} + + +int test_strncmp_str_lit_var (const char *s, long n) +{ + if (strncmp (s, "123456", n) == 0) // { dg-bogus "\\\[-Wstring-compare" } + return 1; + + return 0; +} + +int test_strlen_strncmp_str_lit_var (const char *s, long n) +{ + if (__builtin_strlen (s) < n) + return -1; + + if (n == 6) + if (strncmp (s, "123456", n) == 0) // { dg-bogus "\\\[-Wstring-compare" } + return 1; + + return 0; +} + + diff --git a/gcc/testsuite/gcc.dg/Wstring-compare.c b/gcc/testsuite/gcc.dg/Wstring-compare.c new file mode 100644 index 0000000..0ca492d --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstring-compare.c @@ -0,0 +1,181 @@ +/* PR tree-optimization/90879 - fold zero-equality of strcmp between + a longer string and a smaller array + { dg-do compile } + { dg-options "-O2 -Wall -Wextra -ftrack-macro-expansion=0" } */ + +#include "strlenopt.h" + +#define T(a, b) sink (0 == strcmp (a, b), a, b) + +void sink (int, ...); + +struct S { char a4[4], c; }; + +extern char a4[4]; +extern char a5[5]; +extern char b4[4]; + +/* Verify that comparison of string literals with arrays with unknown + content but size that prevents them from comparing equal is diagnosed. */ + +void strcmp_array_lit (void) +{ + if (strcmp (a4, "1234")) // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero" } + // { dg-bogus "in this expreession" "unwanted note" { target *-*-* } .-1 } + sink (0, a4); + + int cmp; + cmp = strcmp (a4, "1234"); // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero" } + if (cmp) // { dg-message "in this expression" } + sink (0, a4); + + T (a4, "4321"); // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero " } + T (a4, "12345"); // { dg-warning "length 5 and an array of size 4 " } + T (a4, "123456"); // { dg-warning "length 6 and an array of size 4 " } + T ("1234", a4); // { dg-warning "length 4 and an array of size 4 " } + T ("12345", a4); // { dg-warning "length 5 and an array of size 4 " } + T ("123456", a4); // { dg-warning "length 6 and an array of size 4 " } +} + + +void strcmp_array_pstr (void) +{ + const char *s4 = "1234"; + + { + if (strcmp (a4, s4)) // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero" } + // { dg-bogus "in this expreession" "unwanted note" { target *-*-* } .-1 } + sink (1, a4); + else + sink (0, a4); + } + + { + int c; + c = strcmp (a4, s4); // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero" } + if (c) // { dg-message "in this expression" } + sink (1, a4); + else + sink (0, a4); + } + + const char *t4 = "4321"; + const char *s5 = "12345"; + const char *s6 = "123456"; + + T (a4, t4); // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero " } + T (a4, s5); // { dg-warning "length 5 and an array of size 4 " } + T (a4, s6); // { dg-warning "length 6 and an array of size 4 " } + T (s4, a4); // { dg-warning "length 4 and an array of size 4 " } + T (s5, a4); // { dg-warning "length 5 and an array of size 4 " } + T (s6, a4); // { dg-warning "length 6 and an array of size 4 " } +} + + +void strcmp_array_cond_pstr (int i) +{ + const char *s4 = i ? "1234" : "4321"; + T (a4, s4); // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero " } + T (a5, s4); +} + +void strcmp_array_copy (void) +{ + char s[8]; + + { + strcpy (s, "1234"); + if (strcmp (a4, s)) // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero" } + // { dg-bogus "in this expreession" "unwanted note" { target *-*-* } .-1 } + sink (1, a4); + else + sink (0, a4); + } + + { + strcpy (s, "1234"); + + int c; + c = strcmp (a4, s); // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero" } + if (c) // { dg-message "in this expression" } + sink (1, a4); + else + sink (0, a4); + } + + strcpy (s, "4321"); + T (a4, s); // { dg-warning "'strcmp' of a string of length 4 and an array of size 4 evaluates to nonzero " } + strcpy (s, "12345"); + T (a4, s); // { dg-warning "length 5 and an array of size 4 " } + strcpy (s, "123456"); + T (a4, s); // { dg-warning "length 6 and an array of size 4 " } + strcpy (s, "4321"); + T (s, a4); // { dg-warning "length 4 and an array of size 4 " } + strcpy (s, "54321"); + T (s, a4); // { dg-warning "length 5 and an array of size 4 " } + strcpy (s, "654321"); + T (s, a4); // { dg-warning "length 6 and an array of size 4 " } +} + + +void strcmp_member_array_lit (const struct S *p) +{ + T (p->a4, "1234"); // { dg-warning "length 4 and an array of size 4 " } +} + + +#undef T +#define T(a, b, n) sink (0 == strncmp (a, b, n), a, b) + +void strncmp_array_lit (void) +{ + if (strncmp (a4, "12345", 5)) // { dg-warning "'strncmp' of a string of length 5, an array of size 4 and bound of 5 evaluates to nonzero" } + // { dg-bogus "in this expreession" "unwanted note" { target *-*-* } .-1 } + sink (0, a4); + + int cmp; + cmp = strncmp (a4, "54321", 5); // { dg-warning "'strncmp' of a string of length 5, an array of size 4 and bound of 5 evaluates to nonzero" } + if (cmp) // { dg-message "in this expression" } + sink (0, a4); + + // Verify no warning when the bound is the same as the array size. + T (a4, "4321", 4); + T (a4, "654321", 4); + + T (a4, "12345", 5); // { dg-warning "length 5, an array of size 4 and bound of 5 " } + T (a4, "123456", 6); // { dg-warning "length 6, an array of size 4 and bound of 6" } + + T ("1234", a4, 4); + T ("12345", a4, 4); + + T ("12345", a4, 5); // { dg-warning "length 5, an array of size 4 and bound of 5 " } + T ("123456", a4, 6); // { dg-warning "length 6, an array of size 4 and bound of 6 " } +} + + +void strncmp_strarray_copy (void) +{ + { + char a[] = "1234"; + char b[6]; + strcpy (b, "12345"); + if (strncmp (a, b, 5)) // { dg-warning "'strncmp' of strings of length 4 and 5 and bound of 5 evaluates to nonzero" } + // { dg-bogus "in this expreession" "unwanted note" { target *-*-* } .-1 } + sink (0, a, b); + } + + { + char a[] = "4321"; + char b[6]; + strcpy (b, "54321"); + int cmp; + cmp = strncmp (a, b, 5); // { dg-warning "'strncmp' of strings of length 4 and 5 and bound of 5 evaluates to nonzero" } + if (cmp) // { dg-message "in this expression" } + sink (0, a, b); + } + + strcpy (a4, "abc"); + T (a4, "54321", 5); // { dg-warning "'strncmp' of strings of length 3 and 5 and bound of 5 evaluates to nonzero " } +} + + diff --git a/gcc/testsuite/gcc.dg/strcmpopt_3.c b/gcc/testsuite/gcc.dg/strcmpopt_3.c index 571646c..35941be 100644 --- a/gcc/testsuite/gcc.dg/strcmpopt_3.c +++ b/gcc/testsuite/gcc.dg/strcmpopt_3.c @@ -1,31 +1,31 @@ /* { dg-do run } */ -/* { dg-options "-O2 -fdump-tree-strlen" } */ +/* { dg-options "-O2 -fdump-tree-optimized" } */ -__attribute__ ((noinline)) int -f1 (void) -{ +__attribute__ ((noinline)) int +f1 (void) +{ char *s0= "abcd"; char s[8]; __builtin_strcpy (s, s0); - return __builtin_strcmp(s, "abc") != 0; + return __builtin_strcmp (s, "abc") != 0; } __attribute__ ((noinline)) int -f2 (void) -{ +f2 (void) +{ char *s0 = "ab"; char s[8]; __builtin_strcpy (s, s0); - return __builtin_strcmp("abc", s) != 0; + return __builtin_strcmp ("abc", s) != 0; } int main (void) { - if (f1 () != 1 + if (f1 () != 1 || f2 () != 1) __builtin_abort (); return 0; } -/* { dg-final { scan-tree-dump-times "strcmp" 0 "strlen1" } } */ +/* { dg-final { scan-tree-dump-times "strcmp" 0 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/strcmpopt_6.c b/gcc/testsuite/gcc.dg/strcmpopt_6.c new file mode 100644 index 0000000..cb99294 --- /dev/null +++ b/gcc/testsuite/gcc.dg/strcmpopt_6.c @@ -0,0 +1,207 @@ +/* Verify that strcmp and strncmp calls with mixed constant and + non-constant strings are evaluated correctly. + { dg-do run } + { dg-options "-O2" } */ + +#include "strlenopt.h" + +#define A(expr) \ + ((expr) \ + ? (void)0 \ + : (__builtin_printf ("assertion failed on line %i: %s\n", \ + __LINE__, #expr), \ + __builtin_abort ())) + +__attribute__ ((noclone, noinline)) int +test_strlen_gt2_strcmp_abcd (const char *s) +{ + if (strlen (s) < 3) + return -1; + + return strcmp (s, "abcd") == 0; +} + +__attribute__ ((noclone, noinline)) int +test_strlen_lt6_strcmp_abcd (const char *s) +{ + if (strlen (s) > 5) + return -1; + + return strcmp (s, "abcd") == 0; +} + +__attribute__ ((noclone, noinline)) int +test_strcpy_strcmp_abc (const char *s) +{ + char a[4]; + strcpy (a, s); + return strcmp (a, "abc") == 0; +} + +__attribute__ ((noclone, noinline)) int +test_strcpy_abc_strcmp (const char *s) +{ + char a[4], b[6]; + strcpy (a, "abc"); + strcpy (b, s); + return strcmp (a, b) == 0; +} + +/* Exercise strcmp of two strings between 1 and 3 characters long + stored in arrays of the same known size. */ +char ga4[4], gb4[4]; + +__attribute__ ((noclone, noinline)) int +test_store_0_nulterm_strcmp_same_size_arrays (void) +{ + ga4[0] = gb4[0] = 'x'; + ga4[3] = gb4[3] = '\0'; + return strcmp (ga4, gb4) == 0; +} + +__attribute__ ((noclone, noinline)) int +test_store_0_nulterm_strncmp_bound_2_same_size_arrays (void) +{ + ga4[0] = gb4[0] = 'x'; + ga4[3] = gb4[3] = '\0'; + return strncmp (ga4, gb4, 2) == 0; +} + +__attribute__ ((noclone, noinline)) int +test_store_0_nulterm_strncmp_bound_equal_same_size_arrays (void) +{ + ga4[0] = gb4[0] = 'x'; + ga4[3] = gb4[3] = '\0'; + return strncmp (ga4, gb4, 4) == 0; +} + +/* Exercise strcmp of two strings between 0 and 3 characters long + stored in arrays of the same known size. */ + +__attribute__ ((noclone, noinline)) int +test_nulterm_strcmp_same_size_arrays (void) +{ + ga4[3] = gb4[3] = '\0'; + return strcmp (ga4, gb4) == 0; +} + +/* Exercise strcmp of two strings between 1 and 3 and 1 and 4 characters + long, respectively, stored in arrays of known but different sizes. */ +char gc5[5]; + +__attribute__ ((noclone, noinline)) int +test_store_0_nulterm_strcmp_arrays (void) +{ + ga4[0] = gc5[0] = 'x'; + ga4[3] = gc5[4] = '\0'; + return strcmp (ga4, gc5) == 0; +} + +/* Exercise strcmp of two strings between 0 and 3 and 1 and 4 characters + long, respectively, stored in arrays of known but different sizes. */ + +__attribute__ ((noclone, noinline)) int +test_nulterm_strcmp_arrays (void) +{ + ga4[3] = gc5[4] = '\0'; + return strcmp (ga4, gc5) == 0; +} + + +__attribute__ ((noclone, noinline)) int +test_strcpy_strncmp_abcd (const char *s) +{ + char a[6]; + strcpy (a, s); + return strcmp (a, "abcd") == 0; +} + +__attribute__ ((noclone, noinline)) int +test_strcpy_abcd_strncmp_3 (const char *s) +{ + char a[6], b[8]; + strcpy (a, "abcd"); + strcpy (b, s); + return strncmp (a, b, 3) == 0; +} + +__attribute__ ((noclone, noinline)) int +test_strcpy_abcd_strncmp_4 (const char *s) +{ + char a[6], b[8]; + strcpy (a, "abcd"); + strcpy (b, s); + return strncmp (a, b, 4) == 0; +} + + +int main (void) +{ + test_strlen_gt2_strcmp_abcd ("abcd"); + test_strlen_lt6_strcmp_abcd ("abcd"); + + A (0 == test_strcpy_strcmp_abc ("ab")); + A (0 != test_strcpy_strcmp_abc ("abc")); + A (0 == test_strcpy_strcmp_abc ("abcd")); + + A (0 == test_strcpy_abc_strcmp ("ab")); + A (0 != test_strcpy_abc_strcmp ("abc")); + A (0 == test_strcpy_abc_strcmp ("abcd")); + + strcpy (ga4, "abc"); strcpy (gb4, "abd"); + A (0 == test_store_0_nulterm_strcmp_same_size_arrays ()); + strcpy (ga4, "abd"); strcpy (gb4, "abc"); + A (0 == test_store_0_nulterm_strcmp_same_size_arrays ()); + strcpy (ga4, "abc"); strcpy (gb4, "abc"); + A (0 != test_store_0_nulterm_strcmp_same_size_arrays ()); + + strcpy (ga4, "abc"); strcpy (gb4, "acd"); + A (0 == test_store_0_nulterm_strncmp_bound_2_same_size_arrays ()); + strcpy (ga4, "acd"); strcpy (gb4, "abc"); + A (0 == test_store_0_nulterm_strncmp_bound_2_same_size_arrays ()); + strcpy (ga4, "abc"); strcpy (gb4, "abc"); + A (0 != test_store_0_nulterm_strncmp_bound_2_same_size_arrays ()); + + strcpy (ga4, "abc"); strcpy (gb4, "abd"); + A (0 == test_store_0_nulterm_strncmp_bound_equal_same_size_arrays ()); + strcpy (ga4, "abd"); strcpy (gb4, "abc"); + A (0 == test_store_0_nulterm_strncmp_bound_equal_same_size_arrays ()); + strcpy (ga4, "abc"); strcpy (gb4, "abc"); + A (0 != test_store_0_nulterm_strncmp_bound_equal_same_size_arrays ()); + + strcpy (ga4, "abc"); strcpy (gb4, "abd"); + A (0 == test_nulterm_strcmp_same_size_arrays ()); + strcpy (ga4, "abd"); strcpy (gb4, "abc"); + A (0 == test_nulterm_strcmp_same_size_arrays ()); + strcpy (ga4, "abc"); strcpy (gb4, "abc"); + A (0 != test_nulterm_strcmp_same_size_arrays ()); + + strcpy (ga4, "abc"); strcpy (gc5, "abcd"); + A (0 == test_store_0_nulterm_strcmp_arrays ()); + strcpy (ga4, "abd"); strcpy (gc5, "abcd"); + A (0 == test_store_0_nulterm_strcmp_arrays ()); + strcpy (ga4, "abc"); strcpy (gc5, "abc"); + A (0 != test_store_0_nulterm_strcmp_arrays ()); + + strcpy (ga4, "abc"); strcpy (gc5, "abcd"); + A (0 == test_nulterm_strcmp_arrays ()); + strcpy (ga4, "abd"); strcpy (gc5, "abc"); + A (0 == test_nulterm_strcmp_arrays ()); + strcpy (ga4, "abc"); strcpy (gc5, "abc"); + A (0 != test_nulterm_strcmp_arrays ()); + + A (0 == test_strcpy_strncmp_abcd ("ab")); + A (0 == test_strcpy_strncmp_abcd ("abc")); + A (0 != test_strcpy_strncmp_abcd ("abcd")); + A (0 == test_strcpy_strncmp_abcd ("abcde")); + + A (0 == test_strcpy_abcd_strncmp_3 ("ab")); + A (0 != test_strcpy_abcd_strncmp_3 ("abc")); + A (0 != test_strcpy_abcd_strncmp_3 ("abcd")); + A (0 != test_strcpy_abcd_strncmp_3 ("abcde")); + + A (0 == test_strcpy_abcd_strncmp_4 ("ab")); + A (0 == test_strcpy_abcd_strncmp_4 ("abc")); + A (0 != test_strcpy_abcd_strncmp_4 ("abcd")); + A (0 != test_strcpy_abcd_strncmp_4 ("abcde")); +} diff --git a/gcc/testsuite/gcc.dg/strlenopt-65.c b/gcc/testsuite/gcc.dg/strlenopt-65.c index a34d178..521d7ac 100644 --- a/gcc/testsuite/gcc.dg/strlenopt-65.c +++ b/gcc/testsuite/gcc.dg/strlenopt-65.c @@ -1,17 +1,10 @@ /* PRE tree-optimization/90626 - fold strcmp(a, b) == 0 to zero when one string length is exact and the other is unequal { dg-do compile } - { dg-options "-O2 -Wall -fdump-tree-optimized" } */ + { dg-options "-O2 -Wall -Wno-string-compare -fdump-tree-optimized -ftrack-macro-expansion=0" } */ #include "strlenopt.h" -typedef __SIZE_TYPE__ size_t; - -extern void abort (void); -extern void* memcpy (void *, const void *, size_t); -extern int strcmp (const char *, const char *); -extern int strncmp (const char *, const char *, size_t); - #define CAT(x, y) x ## y #define CONCAT(x, y) CAT (x, y) #define FAILNAME(name) CONCAT (call_ ## name ##_on_line_, __LINE__) @@ -142,21 +135,45 @@ void test_strcmp_keep (const char *s, const char *t) #undef CMPFUNC #define CMPFUNC(a, b, dummy) strcmp (a, b) - KEEP ("1", "1", a, b, -1); + KEEP ("123", "123\0", a, b, /* bnd = */ -1); + KEEP ("123\0", "123", a, b, -1); + + { + char a[8], b[8]; + sink (a, b); + strcpy (a, s); + strcpy (b, t); + TEST_KEEP (0 == strcmp (a, b)); + } +} + + +void test_strncmp_keep (const char *s, const char *t) +{ +#undef CMPFUNC +#define CMPFUNC(a, b, n) strncmp (a, b, n) + + KEEP ("1", "1", a, b, 2); - KEEP ("1\0", "1", a, b, -1); - KEEP ("1", "1\0", a, b, -1); + KEEP ("1\0", "1", a, b, 2); + KEEP ("1", "1\0", a, b, 2); - KEEP ("12\0", "12", a, b, -1); - KEEP ("12", "12\0", a, b, -1); + KEEP ("12\0", "12", a, b, 2); + KEEP ("12", "12\0", a, b, 2); - KEEP ("111\0", "111", a, b, -1); - KEEP ("112", "112\0", a, b, -1); + KEEP ("111\0", "111", a, b, 3); + KEEP ("112", "112\0", a, b, 3); - KEEP (s, t, a, b, -1); + { + char a[8], b[8]; + sink (a, b); + strcpy (a, s); + strcpy (b, t); + TEST_KEEP (0 == strncmp (a, b, sizeof a)); + } } /* { dg-final { scan-tree-dump-times "call_in_true_branch_not_eliminated_" 0 "optimized" } } - { dg-final { scan-tree-dump-times "call_made_in_true_branch_on_line_1\[0-9\]\[0-9\]\[0-9\]" 8 "optimized" } } - { dg-final { scan-tree-dump-times "call_made_in_false_branch_on_line_1\[0-9\]\[0-9\]\[0-9\]" 8 "optimized" } } */ + { dg-final { scan-tree-dump-times "call_made_in_true_branch_on_line_1\[0-9\]\[0-9\]\[0-9\]" 11 "optimized" } } + { dg-final { scan-tree-dump-times "call_made_in_false_branch_on_line_1\[0-9\]\[0-9\]\[0-9\]" 11 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/strlenopt-66.c b/gcc/testsuite/gcc.dg/strlenopt-66.c index 5dc10a0..4ba31a8 100644 --- a/gcc/testsuite/gcc.dg/strlenopt-66.c +++ b/gcc/testsuite/gcc.dg/strlenopt-66.c @@ -1,6 +1,6 @@ /* PRE tree-optimization/90626 - fold strcmp(a, b) == 0 to zero when one string length is exact and the other is unequal - { dg-do compile } + { dg-do run } { dg-options "-O2 -Wall -fdump-tree-optimized" } */ #include "strlenopt.h" @@ -65,8 +65,44 @@ test_strncmp (void) A (0 < strncmp (b, a, 5)); } + +__attribute__ ((noclone, noinline, noipa)) void +test_strncmp_a4_cond_s5_s2_2 (const char *s, int i) +{ + char a4[4]; + strcpy (a4, s); + A (0 == strncmp (a4, i ? "12345" : "12", 2)); +} + + +__attribute__ ((noclone, noinline, noipa)) void +test_strncmp_a4_cond_a5_s2_5 (const char *s, const char *t, int i) +{ + char a4[4], a5[5]; + strcpy (a4, s); + strcpy (a5, t); + A (0 == strncmp (a4, i ? a5 : "12", 5)); +} + +__attribute__ ((noclone, noinline, noipa)) void +test_strncmp_a4_cond_a5_a3_n (const char *s1, const char *s2, const char *s3, + int i, unsigned n) +{ + char a3[3], a4[4], a5[5]; + strcpy (a3, s1); + strcpy (a4, s2); + strcpy (a5, s3); + A (0 == strncmp (a4, i ? a5 : a3, n)); +} + + int main (void) { test_strcmp (); test_strncmp (); + test_strncmp_a4_cond_s5_s2_2 ("12", 0); + test_strncmp_a4_cond_a5_s2_5 ("12", "1234", 0); + + test_strncmp_a4_cond_a5_a3_n ("12", "123", "1234", 0, 2); + test_strncmp_a4_cond_a5_a3_n ("123", "12", "12", 1, 3); } diff --git a/gcc/testsuite/gcc.dg/strlenopt-69.c b/gcc/testsuite/gcc.dg/strlenopt-69.c new file mode 100644 index 0000000..46ceb9d --- /dev/null +++ b/gcc/testsuite/gcc.dg/strlenopt-69.c @@ -0,0 +1,126 @@ +/* PR tree-optimization/90879 - fold zero-equality of strcmp between + a longer string and a smaller array + { dg-do compile } + { dg-options "-O2 -Wall -Wno-string-compare -fdump-tree-optimized -ftrack-macro-expansion=0" } */ + +#include "strlenopt.h" + +#define A(expr) \ + ((expr) \ + ? (void)0 \ + : (__builtin_printf ("assertion failed on line %i: %s\n", \ + __LINE__, #expr), \ + __builtin_abort ())) + +void clobber (void*, ...); + +struct S { char a4[4], c; }; + +extern char a4[4]; +extern char b4[4]; + +/* Verify that comparison of string literals with arrays with unknown + content but size that prevents them from comparing equal is folded + to a constant. */ + +void test_array_lit (void) +{ + A (strcmp (a4, "1234")); clobber (a4); + A (strcmp (a4, "12345")); clobber (a4); + A (strcmp (a4, "123456")); clobber (a4); + A (strcmp ("1234", a4)); clobber (a4); + A (strcmp ("12345", a4)); clobber (a4); + A (strcmp ("123456", a4)); clobber (a4); +} + +void test_memarray_lit (struct S *p) +{ + A (strcmp (p->a4, "1234")); + A (strcmp (p->a4, "12345")); + A (strcmp (p->a4, "123456")); + + A (strcmp ("1234", p->a4)); + A (strcmp ("12345", p->a4)); + A (strcmp ("123456", p->a4)); +} + +/* Verify that the equality of empty strings is folded. */ + +void test_empty_string (void) +{ + A (0 == strcmp ("", "")); + + *a4 = '\0'; + A (0 == strcmp (a4, "")); + A (0 == strcmp ("", a4)); + A (0 == strcmp (a4, a4)); + + char s[8] = ""; + A (0 == strcmp (a4, s)); + + a4[1] = '\0'; + b4[1] = '\0'; + A (0 == strcmp (a4 + 1, b4 + 1)); + + a4[2] = '\0'; + b4[2] = '\0'; + A (0 == strcmp (&a4[2], &b4[2])); + + clobber (a4, b4); + + memset (a4, 0, sizeof a4); + memset (b4, 0, sizeof b4); + A (0 == strcmp (a4, b4)); +} + +/* Verify that comparison of dynamically created strings with unknown + arrays is folded. */ + +void test_array_copy (void) +{ + char s[8]; + strcpy (s, "1234"); + A (strcmp (a4, s)); + + strcpy (s, "12345"); + A (strlen (s) == 5); + A (strcmp (a4, s)); clobber (a4); + + strcpy (s, "123456"); + A (strcmp (a4, s)); clobber (a4); + + strcpy (s, "1234"); + A (strcmp (s, a4)); clobber (a4); + + strcpy (s, "12345"); + A (strcmp (s, a4)); clobber (a4); + + strcpy (s, "123456"); + A (strcmp (s, a4)); clobber (a4); +} + + +void test_array_bounded (void) +{ + A (strncmp (a4, "12345", 5)); clobber (a4); + A (strncmp ("54321", a4, 5)); clobber (a4); + + A (strncmp (a4, "123456", 5)); clobber (a4); + A (strncmp ("654321", a4, 5)); clobber (a4); +} + +void test_array_copy_bounded (void) +{ + char s[8]; + strcpy (s, "12345"); + A (strncmp (a4, s, 5)); clobber (a4); + strcpy (s, "54321"); + A (strncmp (s, a4, 5)); clobber (a4); + + strcpy (s, "123456"); + A (strncmp (a4, s, 5)); clobber (a4); + strcpy (s, "654321"); + A (strncmp (s, a4, 5)); clobber (a4); +} + +/* { dg-final { scan-tree-dump-not "abort|strcmp|strncmp" "optimized" } } */ diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index c2866e0..ef2717d 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -915,6 +915,7 @@ get_range_strlen_dynamic (tree src, c_strlen_data *pdata, bitmap *visited, && tree_int_cst_lt (pdata->maxlen, argdata.maxlen))) pdata->maxlen = argdata.maxlen; if (!pdata->maxbound + || TREE_CODE (pdata->maxbound) != INTEGER_CST || (argdata.maxbound && tree_int_cst_lt (pdata->maxbound, argdata.maxbound) @@ -1042,6 +1043,7 @@ get_range_strlen_dynamic (tree src, c_strlen_data *pdata, const vr_values *rvals) { bitmap visited = NULL; + tree maxbound = pdata->maxbound; unsigned limit = PARAM_VALUE (PARAM_SSA_NAME_DEF_CHAIN_LIMIT); if (!get_range_strlen_dynamic (src, pdata, &visited, rvals, &limit)) @@ -1055,6 +1057,11 @@ get_range_strlen_dynamic (tree src, c_strlen_data *pdata, else if (!pdata->minlen) pdata->minlen = ssize_int (0); + /* If it's unchanged from it initial non-null value, set the conservative + MAXBOUND to SIZE_MAX. Otherwise leave it null (if it is null). */ + if (maxbound && pdata->maxbound == maxbound) + pdata->maxbound = build_all_ones_cst (size_type_node); + if (visited) BITMAP_FREE (visited); } @@ -2454,6 +2461,9 @@ maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt) else { c_strlen_data lendata = { }; + /* Set MAXBOUND to an arbitrary non-null non-integer node as a request + to have it set to the length of the longest string in a PHI. */ + lendata.maxbound = src; get_range_strlen (src, &lendata, /* eltsize = */1); if (TREE_CODE (lendata.minlen) == INTEGER_CST && TREE_CODE (lendata.maxbound) == INTEGER_CST) @@ -3226,51 +3236,78 @@ handle_builtin_memset (gimple_stmt_iterator *gsi) return true; } -/* Handle a call to memcmp. We try to handle small comparisons by - converting them to load and compare, and replacing the call to memcmp - with a __builtin_memcmp_eq call where possible. - return true when call is transformed, return false otherwise. */ +/* Return a pointer to the first such equality expression if RES is used + only in expressions testing its equality to zero, and null otherwise. */ -static bool -handle_builtin_memcmp (gimple_stmt_iterator *gsi) +static gimple * +used_only_for_zero_equality (tree res) { - gcall *stmt2 = as_a (gsi_stmt (*gsi)); - tree res = gimple_call_lhs (stmt2); - tree arg1 = gimple_call_arg (stmt2, 0); - tree arg2 = gimple_call_arg (stmt2, 1); - tree len = gimple_call_arg (stmt2, 2); - unsigned HOST_WIDE_INT leni; + gimple *first_use = NULL; + use_operand_p use_p; imm_use_iterator iter; - if (!res) - return false; - FOR_EACH_IMM_USE_FAST (use_p, iter, res) { - gimple *ustmt = USE_STMT (use_p); + gimple *use_stmt = USE_STMT (use_p); - if (is_gimple_debug (ustmt)) - continue; - if (gimple_code (ustmt) == GIMPLE_ASSIGN) + if (is_gimple_debug (use_stmt)) + continue; + if (gimple_code (use_stmt) == GIMPLE_ASSIGN) { - gassign *asgn = as_a (ustmt); - tree_code code = gimple_assign_rhs_code (asgn); - if ((code != EQ_EXPR && code != NE_EXPR) - || !integer_zerop (gimple_assign_rhs2 (asgn))) - return false; + tree_code code = gimple_assign_rhs_code (use_stmt); + if (code == COND_EXPR) + { + tree cond_expr = gimple_assign_rhs1 (use_stmt); + if ((TREE_CODE (cond_expr) != EQ_EXPR + && (TREE_CODE (cond_expr) != NE_EXPR)) + || !integer_zerop (TREE_OPERAND (cond_expr, 1))) + return NULL; + } + else if (code == EQ_EXPR || code == NE_EXPR) + { + if (!integer_zerop (gimple_assign_rhs2 (use_stmt))) + return NULL; + } + else + return NULL; } - else if (gimple_code (ustmt) == GIMPLE_COND) + else if (gimple_code (use_stmt) == GIMPLE_COND) { - tree_code code = gimple_cond_code (ustmt); + tree_code code = gimple_cond_code (use_stmt); if ((code != EQ_EXPR && code != NE_EXPR) - || !integer_zerop (gimple_cond_rhs (ustmt))) - return false; + || !integer_zerop (gimple_cond_rhs (use_stmt))) + return NULL; } else - return false; + return NULL; + + if (!first_use) + first_use = use_stmt; } + return first_use; +} + +/* Handle a call to memcmp. We try to handle small comparisons by + converting them to load and compare, and replacing the call to memcmp + with a __builtin_memcmp_eq call where possible. + return true when call is transformed, return false otherwise. */ + +static bool +handle_builtin_memcmp (gimple_stmt_iterator *gsi) +{ + gcall *stmt = as_a (gsi_stmt (*gsi)); + tree res = gimple_call_lhs (stmt); + + if (!res || !used_only_for_zero_equality (res)) + return false; + + tree arg1 = gimple_call_arg (stmt, 0); + tree arg2 = gimple_call_arg (stmt, 1); + tree len = gimple_call_arg (stmt, 2); + unsigned HOST_WIDE_INT leni; + if (tree_fits_uhwi_p (len) && (leni = tree_to_uhwi (len)) <= GET_MODE_SIZE (word_mode) && pow2p_hwi (leni)) @@ -3283,7 +3320,7 @@ handle_builtin_memcmp (gimple_stmt_iterator *gsi) if (int_mode_for_size (leni, 1).exists (&mode) && (align >= leni || !targetm.slow_unaligned_access (mode, align))) { - location_t loc = gimple_location (stmt2); + location_t loc = gimple_location (stmt); tree type, off; type = build_nonstandard_integer_type (leni, 1); gcc_assert (known_eq (GET_MODE_BITSIZE (TYPE_MODE (type)), leni)); @@ -3307,78 +3344,10 @@ handle_builtin_memcmp (gimple_stmt_iterator *gsi) } } - gimple_call_set_fndecl (stmt2, builtin_decl_explicit (BUILT_IN_MEMCMP_EQ)); + gimple_call_set_fndecl (stmt, builtin_decl_explicit (BUILT_IN_MEMCMP_EQ)); return true; } -/* If IDX1 and IDX2 refer to strings A and B of unequal lengths, return - the result of 0 == strncmp (A, B, N) (which is the same as strcmp for - sufficiently large N). Otherwise return false. */ - -static bool -strxcmp_unequal (int idx1, int idx2, unsigned HOST_WIDE_INT n) -{ - unsigned HOST_WIDE_INT len1; - unsigned HOST_WIDE_INT len2; - - bool nulterm1; - bool nulterm2; - - if (idx1 < 0) - { - len1 = ~idx1; - nulterm1 = true; - } - else if (strinfo *si = get_strinfo (idx1)) - { - if (tree_fits_uhwi_p (si->nonzero_chars)) - { - len1 = tree_to_uhwi (si->nonzero_chars); - nulterm1 = si->full_string_p; - } - else - return false; - } - else - return false; - - if (idx2 < 0) - { - len2 = ~idx2; - nulterm2 = true; - } - else if (strinfo *si = get_strinfo (idx2)) - { - if (tree_fits_uhwi_p (si->nonzero_chars)) - { - len2 = tree_to_uhwi (si->nonzero_chars); - nulterm2 = si->full_string_p; - } - else - return false; - } - else - return false; - - /* N is set to UHWI_MAX for strcmp and less to strncmp. Adjust - the length of each string to consider to be no more than N. */ - if (len1 > n) - len1 = n; - if (len2 > n) - len2 = n; - - if ((len1 < len2 && nulterm1) - || (len2 < len1 && nulterm2)) - /* The string lengths are definitely unequal and the result can - be folded to one (since it's used for comparison with zero). */ - return true; - - /* The string lengths may be equal or unequal. Even when equal and - both strings nul-terminated, without the string contents there's - no way to determine whether they are equal. */ - return false; -} - /* Given an index to the strinfo vector, compute the string length for the corresponding string. Return -1 when unknown. */ @@ -3407,15 +3376,16 @@ compute_string_length (int idx) /* Determine the minimum size of the object referenced by DEST expression which must have a pointer type. - Return the minimum size of the object if successful or NULL when the size - cannot be determined. */ -static tree + Return the minimum size of the object if successful or HWI_M1U when + the size cannot be determined. */ + +static unsigned HOST_WIDE_INT determine_min_objsize (tree dest) { unsigned HOST_WIDE_INT size = 0; if (compute_builtin_object_size (dest, 2, &size)) - return build_int_cst (sizetype, size); + return size; /* Try to determine the size of the object through the RHS of the assign statement. */ @@ -3423,11 +3393,11 @@ determine_min_objsize (tree dest) { gimple *stmt = SSA_NAME_DEF_STMT (dest); if (!is_gimple_assign (stmt)) - return NULL_TREE; + return HOST_WIDE_INT_M1U; if (!gimple_assign_single_p (stmt) && !gimple_assign_unary_nop_p (stmt)) - return NULL_TREE; + return HOST_WIDE_INT_M1U; dest = gimple_assign_rhs1 (stmt); return determine_min_objsize (dest); @@ -3435,7 +3405,7 @@ determine_min_objsize (tree dest) /* Try to determine the size of the object from its type. */ if (TREE_CODE (dest) != ADDR_EXPR) - return NULL_TREE; + return HOST_WIDE_INT_M1U; tree type = TREE_TYPE (dest); if (TREE_CODE (type) == POINTER_TYPE) @@ -3443,196 +3413,388 @@ determine_min_objsize (tree dest) type = TYPE_MAIN_VARIANT (type); - /* We cannot determine the size of the array if it's a flexible array, - which is declared at the end of a structure. */ - if (TREE_CODE (type) == ARRAY_TYPE - && !array_at_struct_end_p (dest)) + /* The size of a flexible array cannot be determined. Otherwise, + for arrays with more than one element, return the size of its + type. GCC itself misuses arrays of both zero and one elements + as flexible array members so they are excluded as well. */ + if (TREE_CODE (type) != ARRAY_TYPE + || !array_at_struct_end_p (dest)) { - tree size_t = TYPE_SIZE_UNIT (type); - if (size_t && TREE_CODE (size_t) == INTEGER_CST - && !integer_zerop (size_t)) - return size_t; + tree type_size = TYPE_SIZE_UNIT (type); + if (type_size && TREE_CODE (type_size) == INTEGER_CST + && !integer_onep (type_size) + && !integer_zerop (type_size)) + return tree_to_uhwi (type_size); } - return NULL_TREE; + return HOST_WIDE_INT_M1U; } -/* Handle a call to strcmp or strncmp. When the result is ONLY used to do - equality test against zero: - - A. When the lengths of both arguments are constant and it's a strcmp: - * if the lengths are NOT equal, we can safely fold the call - to a non-zero value. - * otherwise, do nothing now. - - B. When the length of one argument is constant, try to replace the call - with a __builtin_str(n)cmp_eq call where possible, i.e: - - strncmp (s, STR, C) (!)= 0 in which, s is a pointer to a string, STR - is a string with constant length , C is a constant. - if (C <= strlen(STR) && sizeof_array(s) > C) - { - replace this call with - strncmp_eq (s, STR, C) (!)= 0 - } - if (C > strlen(STR) - { - it can be safely treated as a call to strcmp (s, STR) (!)= 0 - can handled by the following strcmp. - } - - strcmp (s, STR) (!)= 0 in which, s is a pointer to a string, STR - is a string with constant length. - if (sizeof_array(s) > strlen(STR)) - { - replace this call with - strcmp_eq (s, STR, strlen(STR)+1) (!)= 0 - } - - Return true when the call is transformed, return false otherwise. - */ +/* Given strinfo IDX for ARG, set LENRNG[] to the range of lengths + of the string(s) referenced by ARG if it can be determined. + If the length cannot be determined, set *SIZE to the size of + the array the string is stored in, if any. If no such array is + known, set *SIZE to -1. When the strings are nul-terminated set + *NULTERM to true, otherwise to false. Return true on success. */ static bool -handle_builtin_string_cmp (gimple_stmt_iterator *gsi) +get_len_or_size (tree arg, int idx, unsigned HOST_WIDE_INT lenrng[2], + unsigned HOST_WIDE_INT *size, bool *nulterm) { - gcall *stmt = as_a (gsi_stmt (*gsi)); - tree res = gimple_call_lhs (stmt); - use_operand_p use_p; - imm_use_iterator iter; - tree arg1 = gimple_call_arg (stmt, 0); - tree arg2 = gimple_call_arg (stmt, 1); - int idx1 = get_stridx (arg1); - int idx2 = get_stridx (arg2); - HOST_WIDE_INT length = -1; - bool is_ncmp = false; + /* Set so that both LEN and ~LEN are invalid lengths, i.e., + maximum possible length + 1. */ + lenrng[0] = lenrng[1] = HOST_WIDE_INT_MAX; - if (!res) - return false; - - /* When both arguments are unknown, do nothing. */ - if (idx1 == 0 && idx2 == 0) - return false; + *size = HOST_WIDE_INT_M1U; - /* Handle strncmp function. */ - if (gimple_call_num_args (stmt) == 3) + if (idx < 0) { - tree len = gimple_call_arg (stmt, 2); - if (tree_fits_shwi_p (len)) - length = tree_to_shwi (len); - - is_ncmp = true; + /* IDX is the inverted constant string length. */ + lenrng[0] = ~idx; + lenrng[1] = lenrng[0]; + *nulterm = true; } - - /* For strncmp, if the length argument is NOT known, do nothing. */ - if (is_ncmp && length < 0) - return false; - - /* When the result is ONLY used to do equality test against zero. */ - FOR_EACH_IMM_USE_FAST (use_p, iter, res) + else if (idx == 0) + ; /* Handled below. */ + else if (strinfo *si = get_strinfo (idx)) { - gimple *use_stmt = USE_STMT (use_p); + if (!si->nonzero_chars) + arg = si->ptr; + else if (tree_fits_uhwi_p (si->nonzero_chars)) + { + lenrng[0] = tree_to_uhwi (si->nonzero_chars); + *nulterm = si->full_string_p; + /* Set the upper bound only if the string is known to be + nul-terminated, otherwise leave it at maximum + 1. */ + if (*nulterm) + lenrng[1] = lenrng[0]; + } + else if (TREE_CODE (si->nonzero_chars) == SSA_NAME) + { + wide_int min, max; + value_range_kind rng = get_range_info (si->nonzero_chars, &min, &max); + if (rng == VR_RANGE) + { + lenrng[0] = min.to_uhwi (); + lenrng[1] = max.to_uhwi (); + *nulterm = si->full_string_p; + } + } + else if (si->ptr) + arg = si->ptr; + } - if (is_gimple_debug (use_stmt)) - continue; - if (gimple_code (use_stmt) == GIMPLE_ASSIGN) + if (lenrng[0] == HOST_WIDE_INT_MAX) + { + /* Compute the minimum and maximum real or possible lengths. */ + c_strlen_data lendata = { }; + if (get_range_strlen (arg, &lendata, /* eltsize = */1)) { - tree_code code = gimple_assign_rhs_code (use_stmt); - if (code == COND_EXPR) + if (tree_fits_shwi_p (lendata.maxlen) && !lendata.maxbound) { - tree cond_expr = gimple_assign_rhs1 (use_stmt); - if ((TREE_CODE (cond_expr) != EQ_EXPR - && (TREE_CODE (cond_expr) != NE_EXPR)) - || !integer_zerop (TREE_OPERAND (cond_expr, 1))) - return false; + lenrng[0] = tree_to_shwi (lendata.minlen); + lenrng[1] = tree_to_shwi (lendata.maxlen); + *nulterm = true; } - else if (code == EQ_EXPR || code == NE_EXPR) + else if (lendata.maxbound && tree_fits_shwi_p (lendata.maxbound)) { - if (!integer_zerop (gimple_assign_rhs2 (use_stmt))) - return false; - } - else - return false; + /* Set *SIZE to the conservative LENDATA.MAXBOUND which + is a conservative estimate of the longest string based + on the sizes of the arrays referenced by ARG. */ + *size = tree_to_uhwi (lendata.maxbound) + 1; + *nulterm = false; + } } - else if (gimple_code (use_stmt) == GIMPLE_COND) + else { - tree_code code = gimple_cond_code (use_stmt); - if ((code != EQ_EXPR && code != NE_EXPR) - || !integer_zerop (gimple_cond_rhs (use_stmt))) - return false; + /* Set *SIZE to the size of the smallest object referenced + by ARG if ARG denotes a single object, or to HWI_M1U + otherwise. */ + *size = determine_min_objsize (arg); + *nulterm = false; } - else - return false; } - /* When the lengths of the arguments are known to be unequal - we can safely fold the call to a non-zero value for strcmp; - otherwise, do nothing now. */ - if (idx1 != 0 && idx2 != 0) - { - if (strxcmp_unequal (idx1, idx2, length)) - { - replace_call_with_value (gsi, integer_one_node); - return true; - } - return false; + return lenrng[0] != HOST_WIDE_INT_MAX || *size != HOST_WIDE_INT_M1U; +} + +/* If IDX1 and IDX2 refer to strings A and B of unequal lengths, return + the result of 0 == strncmp (A, B, BOUND) (which is the same as strcmp + for a sufficiently large BOUND). If the result is based on the length + of one string being greater than the longest string that would fit in + the array pointer to by the argument, set *PLEN and *PSIZE to + the corresponding length (or its complement when the string is known + to be at least as long and need not be nul-terminated) and size. + Otherwise return null. */ + +static tree +strxcmp_eqz_result (tree arg1, int idx1, tree arg2, int idx2, + unsigned HOST_WIDE_INT bound, unsigned HOST_WIDE_INT len[2], + unsigned HOST_WIDE_INT *psize) +{ + /* Determine the range the length of each string is in and whether it's + known to be nul-terminated, or the size of the array it's stored in. */ + bool nul1, nul2; + unsigned HOST_WIDE_INT siz1, siz2; + unsigned HOST_WIDE_INT len1rng[2], len2rng[2]; + if (!get_len_or_size (arg1, idx1, len1rng, &siz1, &nul1) + || !get_len_or_size (arg2, idx2, len2rng, &siz2, &nul2)) + return NULL_TREE; + + /* BOUND is set to HWI_M1U for strcmp and less to strncmp, and LENiRNG + to HWI_MAX when invalid. Adjust the length of each string to consider + to be no more than BOUND. */ + if (len1rng[0] < HOST_WIDE_INT_MAX && len1rng[0] > bound) + len1rng[0] = bound; + if (len1rng[1] < HOST_WIDE_INT_MAX && len1rng[1] > bound) + len1rng[1] = bound; + if (len2rng[0] < HOST_WIDE_INT_MAX && len2rng[0] > bound) + len2rng[0] = bound; + if (len2rng[1] < HOST_WIDE_INT_MAX && len2rng[1] > bound) + len2rng[1] = bound; + + /* Two empty strings are equal. */ + if (len1rng[1] == 0 && len2rng[1] == 0) + return integer_one_node; + + /* The strings are definitely unequal when the lower bound of the length + of one of them is greater than the length of the longest string that + would fit into the other array. */ + if (len1rng[0] == HOST_WIDE_INT_MAX + && len2rng[0] != HOST_WIDE_INT_MAX + && ((len2rng[0] < bound && len2rng[0] >= siz1) + || len2rng[0] > siz1)) + { + *psize = siz1; + len[0] = len1rng[0]; + /* Set LEN[0] to the lower bound of ARG1's length when it's + nul-terminated or to the complement of its minimum length + otherwise, */ + len[1] = nul2 ? len2rng[0] : ~len2rng[0]; + return integer_zero_node; + } + + if (len2rng[0] == HOST_WIDE_INT_MAX + && len1rng[0] != HOST_WIDE_INT_MAX + && ((len1rng[0] < bound && len1rng[0] >= siz2) + || len1rng[0] > siz2)) + { + *psize = siz2; + len[0] = nul1 ? len1rng[0] : ~len1rng[0]; + len[1] = len2rng[0]; + return integer_zero_node; + } + + /* The strings are also definitely unequal when their lengths are unequal + and at least one is nul-terminated. */ + if (len1rng[0] != HOST_WIDE_INT_MAX + && len2rng[0] != HOST_WIDE_INT_MAX + && ((len1rng[1] < len2rng[0] && nul1) + || (len2rng[1] < len1rng[0] && nul2))) + { + if (bound <= len1rng[0] || bound <= len2rng[0]) + *psize = bound; + else + *psize = HOST_WIDE_INT_M1U; + + len[0] = len1rng[0]; + len[1] = len2rng[0]; + return integer_zero_node; } - /* When the length of one argument is constant. */ - tree var_string = NULL_TREE; - HOST_WIDE_INT const_string_leni = -1; + /* The string lengths may be equal or unequal. Even when equal and + both strings nul-terminated, without the string contents there's + no way to determine whether they are equal. */ + return NULL_TREE; +} + +/* Diagnose pointless calls to strcmp or strncmp STMT with string + arguments of lengths LEN or size SIZ and (for strncmp) BOUND, + whose result is used in equality expressions that evaluate to + a constant due to one argument being longer than the size of + the other. */ - if (idx1) +static void +maybe_warn_pointless_strcmp (gimple *stmt, HOST_WIDE_INT bound, + unsigned HOST_WIDE_INT len[2], + unsigned HOST_WIDE_INT siz) +{ + gimple *use = used_only_for_zero_equality (gimple_call_lhs (stmt)); + if (!use) + return; + + bool at_least = false; + + /* Excessive LEN[i] indicates a lower bound. */ + if (len[0] > HOST_WIDE_INT_MAX) { - const_string_leni = compute_string_length (idx1); - var_string = arg2; + at_least = true; + len[0] = ~len[0]; } - else + + if (len[1] > HOST_WIDE_INT_MAX) { - gcc_checking_assert (idx2); - const_string_leni = compute_string_length (idx2); - var_string = arg1; + at_least = true; + len[1] = ~len[1]; } - if (const_string_leni < 0) - return false; + unsigned HOST_WIDE_INT minlen = MIN (len[0], len[1]); - unsigned HOST_WIDE_INT var_sizei = 0; - /* try to determine the minimum size of the object pointed by var_string. */ - tree size = determine_min_objsize (var_string); + /* FIXME: Include a note pointing to the declaration of the smaller + array. */ + location_t stmt_loc = gimple_location (stmt); + tree callee = gimple_call_fndecl (stmt); + bool warned = false; + if (siz <= minlen && bound == -1) + warned = warning_at (stmt_loc, OPT_Wstring_compare, + (at_least + ? G_("%G%qD of a string of length %wu or more and " + "an array of size %wu evaluates to nonzero") + : G_("%G%qD of a string of length %wu and an array " + "of size %wu evaluates to nonzero")), + stmt, callee, minlen, siz); + else if (!at_least && siz <= HOST_WIDE_INT_MAX) + { + if (len[0] != HOST_WIDE_INT_MAX && len[1] != HOST_WIDE_INT_MAX) + warned = warning_at (stmt_loc, OPT_Wstring_compare, + "%G%qD of strings of length %wu and %wu " + "and bound of %wu evaluates to nonzero", + stmt, callee, len[0], len[1], bound); + else + warned = warning_at (stmt_loc, OPT_Wstring_compare, + "%G%qD of a string of length %wu, an array " + "of size %wu and bound of %wu evaluates to " + "nonzero", + stmt, callee, minlen, siz, bound); + } + + if (warned) + { + location_t use_loc = gimple_location (use); + if (LOCATION_LINE (stmt_loc) != LOCATION_LINE (use_loc)) + inform (use_loc, "in this expression"); + } +} - if (!size) - return false; - if (tree_fits_uhwi_p (size)) - var_sizei = tree_to_uhwi (size); +/* Optimize a call to strcmp or strncmp either by folding it to a constant + when possible or by transforming the latter to the former. Warn about + calls where the length of one argument is greater than the size of + the array to which the other argument points if the latter's length + is not known. Return true when the call has been transformed into + another and false otherwise. */ + +static bool +handle_builtin_string_cmp (gimple_stmt_iterator *gsi) +{ + gcall *stmt = as_a (gsi_stmt (*gsi)); + tree lhs = gimple_call_lhs (stmt); - if (var_sizei == 0) + if (!lhs) return false; - /* For strncmp, if length > const_string_leni , this call can be safely - transformed to a strcmp. */ - if (is_ncmp && length > const_string_leni) - is_ncmp = false; + tree arg1 = gimple_call_arg (stmt, 0); + tree arg2 = gimple_call_arg (stmt, 1); + int idx1 = get_stridx (arg1); + int idx2 = get_stridx (arg2); - unsigned HOST_WIDE_INT final_length - = is_ncmp ? length : const_string_leni + 1; + /* For strncmp set to the the value of the third argument if known. */ + HOST_WIDE_INT bound = -1; - /* Replace strcmp or strncmp with the corresponding str(n)cmp_eq. */ - if (var_sizei > final_length) + /* Extract the strncmp bound. */ + if (gimple_call_num_args (stmt) == 3) { - tree fn - = (is_ncmp - ? builtin_decl_implicit (BUILT_IN_STRNCMP_EQ) - : builtin_decl_implicit (BUILT_IN_STRCMP_EQ)); - if (!fn) + tree len = gimple_call_arg (stmt, 2); + if (tree_fits_shwi_p (len)) + bound = tree_to_shwi (len); + + /* If the bound argument is NOT known, do nothing. */ + if (bound < 0) return false; - tree const_string_len = build_int_cst (size_type_node, final_length); - update_gimple_call (gsi, fn, 3, arg1, arg2, const_string_len); } + + { + /* Set to the length of one argument (or its complement if it's + the lower bound of a range) and the size of the array storing + the other if the result is based on the former being equal to + or greater than the latter. */ + unsigned HOST_WIDE_INT len[2] = { HOST_WIDE_INT_MAX, HOST_WIDE_INT_MAX }; + unsigned HOST_WIDE_INT siz = HOST_WIDE_INT_M1U; + + /* Try to determine if the two strings are either definitely equal + or definitely unequal and if so, either fold the result to zero + (when equal) or set the range of the result to ~[0, 0] otherwise. */ + if (tree eqz = strxcmp_eqz_result (arg1, idx1, arg2, idx2, bound, + len, &siz)) + { + if (integer_zerop (eqz)) + { + maybe_warn_pointless_strcmp (stmt, bound, len, siz); + + /* When the lengths of the first two string arguments are + known to be unequal set the range of the result to non-zero. + This allows the call to be eliminated if its result is only + used in tests for equality to zero. */ + wide_int zero = wi::zero (TYPE_PRECISION (TREE_TYPE (lhs))); + set_range_info (lhs, VR_ANTI_RANGE, zero, zero); + return false; + } + /* When the two strings are definitely equal (such as when they + are both empty) fold the call to the constant result. */ + replace_call_with_value (gsi, integer_zero_node); + return true; + } + } + + /* Return if nothing is known about the strings pointed to by ARG1 + and ARG2. */ + if (idx1 == 0 && idx2 == 0) + return false; + + /* Determine either the length or the size of each of the strings, + whichever is available. */ + HOST_WIDE_INT cstlen1 = -1, cstlen2 = -1; + HOST_WIDE_INT arysiz1 = -1, arysiz2 = -1; + + if (idx1) + cstlen1 = compute_string_length (idx1) + 1; else + arysiz1 = determine_min_objsize (arg1); + + /* Bail if neither the string length nor the size of the array + it is stored in can be determined. */ + if (cstlen1 < 0 && arysiz1 < 0) return false; - return true; + /* Repeat for the second argument. */ + if (idx2) + cstlen2 = compute_string_length (idx2) + 1; + else + arysiz2 = determine_min_objsize (arg2); + + if (cstlen2 < 0 && arysiz2 < 0) + return false; + + /* The exact number of characters to compare. */ + HOST_WIDE_INT cmpsiz = bound < 0 ? cstlen1 < 0 ? cstlen2 : cstlen1 : bound; + /* The size of the array in which the unknown string is stored. */ + HOST_WIDE_INT varsiz = arysiz1 < 0 ? arysiz2 : arysiz1; + + if (cmpsiz < varsiz && used_only_for_zero_equality (lhs)) + { + /* If the known length is less than the size of the other array + and the strcmp result is only used to test equality to zero, + transform the call to the equivalent _eq call. */ + if (tree fn = builtin_decl_implicit (bound < 0 ? BUILT_IN_STRCMP_EQ + : BUILT_IN_STRNCMP_EQ)) + { + tree n = build_int_cst (size_type_node, cmpsiz); + update_gimple_call (gsi, fn, 3, arg1, arg2, n); + return true; + } + } + + return false; } /* Handle a POINTER_PLUS_EXPR statement. -- 2.7.4