From d677a8b6818b1682bfccae40cceb7264b53c1018 Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Tue, 22 May 2018 17:45:35 +0000 Subject: [PATCH] PR c/85623 - strncmp() warns about attribute 'nonstring' incorrectly in -Wstringop-overflow gcc/ChangeLog: PR c/85623 * calls.c (maybe_warn_nonstring_arg): Use string length to set or ajust the presumed bound on an operation to avoid unnecessary warnings. gcc/testsuite/ChangeLog: PR c/85623 * c-c++-common/attr-nonstring-3.c: Adjust. * c-c++-common/attr-nonstring-4.c: Adjust. * c-c++-common/attr-nonstring-6.c: New test. From-SVN: r260541 --- gcc/ChangeLog | 7 + gcc/calls.c | 73 ++++++- gcc/testsuite/ChangeLog | 7 + gcc/testsuite/c-c++-common/attr-nonstring-3.c | 7 +- gcc/testsuite/c-c++-common/attr-nonstring-4.c | 8 +- gcc/testsuite/c-c++-common/attr-nonstring-6.c | 185 ++++++++++++++++++ 6 files changed, 274 insertions(+), 13 deletions(-) create mode 100644 gcc/testsuite/c-c++-common/attr-nonstring-6.c diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 12a305ce13b..3dc05662b7b 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -40,6 +40,13 @@ * config/aarch64/predicates.md (aarch64_reg_zero_or_fp_zero): New predicate. +2018-05-22 Martin Sebor + + PR c/85623 + * calls.c (maybe_warn_nonstring_arg): Use string length to set + or ajust the presumed bound on an operation to avoid unnecessary + warnings. + 2018-05-22 Martin Sebor PR tree-optimization/85826 diff --git a/gcc/calls.c b/gcc/calls.c index f0e9d3b1cbb..d2eecf139e0 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -55,6 +55,7 @@ along with GCC; see the file COPYING3. If not see #include "stringpool.h" #include "attribs.h" #include "builtins.h" +#include "gimple-fold.h" /* Like PREFERRED_STACK_BOUNDARY but in units of bytes, not bits. */ #define STACK_BYTES (PREFERRED_STACK_BOUNDARY / BITS_PER_UNIT) @@ -1616,15 +1617,36 @@ maybe_warn_nonstring_arg (tree fndecl, tree exp) /* The bound argument to a bounded string function like strncpy. */ tree bound = NULL_TREE; + /* The range of lengths of a string argument to one of the comparison + functions. If the length is less than the bound it is used instead. */ + tree lenrng[2] = { NULL_TREE, NULL_TREE }; + /* It's safe to call "bounded" string functions with a non-string argument since the functions provide an explicit bound for this purpose. */ switch (DECL_FUNCTION_CODE (fndecl)) { - case BUILT_IN_STPNCPY: - case BUILT_IN_STPNCPY_CHK: + case BUILT_IN_STRCMP: case BUILT_IN_STRNCMP: case BUILT_IN_STRNCASECMP: + { + /* For these, if one argument refers to one or more of a set + of string constants or arrays of known size, determine + the range of their known or possible lengths and use it + conservatively as the bound for the unbounded function, + and to adjust the range of the bound of the bounded ones. */ + unsigned stride = with_bounds ? 2 : 1; + for (unsigned argno = 0; argno < nargs && !*lenrng; argno += stride) + { + tree arg = CALL_EXPR_ARG (exp, argno); + if (!get_attr_nonstring_decl (arg)) + get_range_strlen (arg, lenrng); + } + } + /* Fall through. */ + + case BUILT_IN_STPNCPY: + case BUILT_IN_STPNCPY_CHK: case BUILT_IN_STRNCPY: case BUILT_IN_STRNCPY_CHK: { @@ -1651,6 +1673,33 @@ maybe_warn_nonstring_arg (tree fndecl, tree exp) if (bound) get_size_range (bound, bndrng); + if (*lenrng) + { + /* Add one for the nul. */ + lenrng[0] = const_binop (PLUS_EXPR, TREE_TYPE (lenrng[0]), + lenrng[0], size_one_node); + lenrng[1] = const_binop (PLUS_EXPR, TREE_TYPE (lenrng[1]), + lenrng[1], size_one_node); + + if (!bndrng[0]) + { + /* Conservatively use the upper bound of the lengths for + both the lower and the upper bound of the operation. */ + bndrng[0] = lenrng[1]; + bndrng[1] = lenrng[1]; + bound = void_type_node; + } + else + { + /* Replace the bound on the oparation with the upper bound + of the length of the string if the latter is smaller. */ + if (tree_int_cst_lt (lenrng[1], bndrng[0])) + bndrng[0] = lenrng[1]; + else if (tree_int_cst_lt (lenrng[1], bndrng[1])) + bndrng[1] = lenrng[1]; + } + } + /* Iterate over the built-in function's formal arguments and check each const char* against the actual argument. If the actual argument is declared attribute non-string issue a warning unless @@ -1693,18 +1742,28 @@ maybe_warn_nonstring_arg (tree fndecl, tree exp) tree type = TREE_TYPE (decl); + /* The maximum number of array elements accessed. */ offset_int wibnd = 0; if (bndrng[0]) wibnd = wi::to_offset (bndrng[0]); + /* Size of the array. */ offset_int asize = wibnd; + /* Determine the array size. For arrays of unknown bound and + pointers reset BOUND to trigger the appropriate warning. */ if (TREE_CODE (type) == ARRAY_TYPE) - if (tree arrbnd = TYPE_DOMAIN (type)) - { - if ((arrbnd = TYPE_MAX_VALUE (arrbnd))) - asize = wi::to_offset (arrbnd) + 1; - } + { + if (tree arrbnd = TYPE_DOMAIN (type)) + { + if ((arrbnd = TYPE_MAX_VALUE (arrbnd))) + asize = wi::to_offset (arrbnd) + 1; + } + else if (bound == void_type_node) + bound = NULL_TREE; + } + else if (bound == void_type_node) + bound = NULL_TREE; location_t loc = EXPR_LOCATION (exp); diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 06ca4806b07..f21a1fb5bcd 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -4,6 +4,13 @@ * gcc.target/aarch64/ldp_stp_7.c: New. * gcc.target/aarch64/ldp_stp_8.c: New. +2018-05-22 Martin Sebor + + PR c/85623 + * c-c++-common/attr-nonstring-3.c: Adjust. + * c-c++-common/attr-nonstring-4.c: Adjust. + * c-c++-common/attr-nonstring-6.c: New test. + 2018-05-22 Martin Sebor PR tree-optimization/85826 diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-3.c b/gcc/testsuite/c-c++-common/attr-nonstring-3.c index 1c50e0dc8fb..a54e73a3285 100644 --- a/gcc/testsuite/c-c++-common/attr-nonstring-3.c +++ b/gcc/testsuite/c-c++-common/attr-nonstring-3.c @@ -47,7 +47,10 @@ char* strndup (const char*, size_t); #define NONSTRING __attribute__ ((nonstring)) -char str[4]; +/* STR needs to be bigger than ARR to trigger warnings, otherwise + since STR must be a string, using both in a string function + can be assumed to be safe even if ARR isn't nul-terminated. */ +char str[5]; char arr[4] NONSTRING; char *ptr; @@ -55,7 +58,7 @@ char *parr NONSTRING; struct MemArrays { - char str[4]; + char str[5]; char arr[4] NONSTRING; char *parr NONSTRING; }; diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-4.c b/gcc/testsuite/c-c++-common/attr-nonstring-4.c index 0571e46fc4d..597bbb32c25 100644 --- a/gcc/testsuite/c-c++-common/attr-nonstring-4.c +++ b/gcc/testsuite/c-c++-common/attr-nonstring-4.c @@ -37,23 +37,23 @@ int warn_strcmp_cst_2 (void) int warn_strncmp_cst_1 (void) { - return strncmp ("bar", ar5, X); /* { dg-warning "argument 2 declared attribute .nonstring." } */ + return strncmp ("12345", ar5, X); /* { dg-warning "argument 2 declared attribute .nonstring." } */ } int warn_strncmp_cst_2 (void) { - return strncmp (ar5, "foo", X); /* { dg-warning "argument 1 declared attribute .nonstring." } */ + return strncmp (ar5, "12345", X); /* { dg-warning "argument 1 declared attribute .nonstring." } */ } int nowarn_strncmp_cst_1 (void) { - return strncmp ("bar", ar5, N); + return strncmp ("12345", ar5, N); } int nowarn_strncmp_cst_2 (void) { - return strncmp (ar5, "foo", N); + return strncmp (ar5, "12345", N); } diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-6.c b/gcc/testsuite/c-c++-common/attr-nonstring-6.c new file mode 100644 index 00000000000..19ceaacf39c --- /dev/null +++ b/gcc/testsuite/c-c++-common/attr-nonstring-6.c @@ -0,0 +1,185 @@ +/* PR 85623 - strncmp() warns about attribute 'nonstring' incorrectly + in -Wstringop-overflow + { dg-do compile } + { dg-options "-O2 -Wstringop-overflow -ftrack-macro-expansion=0" } */ + +#include "../gcc.dg/range.h" + +#if __cplusplus +extern "C" { +#endif + +extern int strcmp (const char*, const char*); +extern int strncmp (const char*, const char*, size_t); +extern int strncasecmp (const char*, const char*, size_t); + +extern size_t strspn (const char*, const char*); +extern size_t strcspn (const char*, const char*); + +#if __cplusplus +} +#endif + +#define S26 "0123456789abcdefghijklmnopqrstuvwxyz" +#define S(n) (S26 + sizeof S26 - 1 - (n)) + +char __attribute__ ((nonstring)) a3[3]; +char __attribute__ ((nonstring)) a5[5]; + +void sink (int); + +#define T(call) sink (call) + +void test_strcmp_cst (void) +{ + /* Verify that no warning is issued for strcmp() calls with a non-string + array argument when the other argument is a string whose length is + less than the size of the array. Because the function stops reading + at the first nul character there is no chance that it will read past + the end of the array. */ + T (strcmp (S (0), a3)); + T (strcmp (S (1), a3)); + T (strcmp (S (2), a3)); + /* The following reads a3[3]. */ + T (strcmp (S (3), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + /* The following also reads past the end of a3. */ + T (strcmp (S (9), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + T (strcmp (a3, S (0))); + T (strcmp (a3, S (1))); + T (strcmp (a3, S (2))); + T (strcmp (a3, S (3))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strcmp (a3, S (9))); /* { dg-warning "\\\[-Wstringop-overflow" } */ +} + + +void test_strcmp_range (const char *s) +{ + s = signed_value () < 0 ? S (0) : S (1); + T (strcmp (a3, s)); + + s = signed_value () < 0 ? S (0) : S (2); + T (strcmp (a3, s)); + + s = signed_value () < 0 ? S (0) : S (3); + T (strcmp (a3, s)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + s = signed_value () < 0 ? S (1) : S (2); + T (strcmp (a3, s)); + + s = signed_value () < 0 ? S (1) : S (3); + T (strcmp (a3, s)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + s = signed_value () < 0 ? S (3) : S (4); + T (strcmp (a3, s)); /* { dg-warning "\\\[-Wstringop-overflow" } */ +} + + +void test_strncmp_cst (void) +{ + T (strncmp (S (0), a3, 1)); + T (strncmp (S (1), a3, 2)); + T (strncmp (S (2), a3, 3)); + T (strncmp (S (3), a3, 3)); + T (strncmp (S (3), a3, 4)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + T (strncmp (S (9), a3, 3)); + T (strncmp (S (9), a3, 4)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strncmp (S (9), a3, 5)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + T (strncmp (a3, S (0), 1)); + T (strncmp (a3, S (1), 2)); + T (strncmp (a3, S (2), 3)); + T (strncmp (a3, S (3), 3)); + T (strncmp (a3, S (3), 4)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + T (strncmp (a3, S (9), 3)); + T (strncmp (a3, S (9), 4)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strncmp (a3, S (9), 5)); /* { dg-warning "\\\[-Wstringop-overflow" } */ +} + +void test_strncmp_range (const char *s) +{ + T (strncmp (a3, S (2), UR (0, 3))); + T (strncmp (a3, S (2), UR (1, 4))); + T (strncmp (a3, S (2), UR (2, 5))); + T (strncmp (a3, S (2), UR (3, 6))); + T (strncmp (a3, S (2), UR (4, 7))); + + T (strncmp (a3, S (5), UR (0, 3))); + T (strncmp (a3, S (5), UR (1, 4))); + T (strncmp (a3, S (5), UR (2, 5))); + T (strncmp (a3, S (5), UR (3, 6))); + T (strncmp (a3, S (5), UR (4, 7))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strncmp (a3, S (5), UR (7, 9))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + s = signed_value () < 0 ? S (0) : S (1); + T (strncmp (a3, s, UR (1, 3))); + T (strncmp (a3, s, UR (2, 5))); + + s = signed_value () < 0 ? S (2) : S (5); + T (strncmp (a3, s, UR (1, 3))); + + s = signed_value () < 0 ? S (2) : S (5); + T (strncmp (a3, s, UR (1, 4))); + T (strncmp (a3, s, UR (2, 5))); + T (strncmp (a3, s, UR (3, 6))); + T (strncmp (a3, s, UR (4, 7))); /* { dg-warning "\\\[-Wstringop-overflow" } */ +} + +void test_strncasecmp (void) +{ + T (strncasecmp (S (0), a3, 1)); + T (strncasecmp (S (1), a3, 2)); + T (strncasecmp (S (2), a3, 3)); + T (strncasecmp (S (3), a3, 3)); + T (strncasecmp (S (3), a3, 4)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + T (strncasecmp (S (9), a3, 3)); + T (strncasecmp (S (9), a3, 4)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strncasecmp (S (9), a3, 5)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + T (strncasecmp (a3, S (0), 1)); + T (strncasecmp (a3, S (1), 2)); + T (strncasecmp (a3, S (2), 3)); + T (strncasecmp (a3, S (3), 3)); + T (strncasecmp (a3, S (3), 4)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + T (strncasecmp (a3, S (9), 3)); + T (strncasecmp (a3, S (9), 4)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strncasecmp (a3, S (9), 5)); /* { dg-warning "\\\[-Wstringop-overflow" } */ +} + +void test_strspn (void) +{ + /* strspn must traverse all characters in the second argument except + when the first string is empty. */ + T (strspn (S (0), a3)); + T (strspn (S (1), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strspn (S (2), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strspn (S (3), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strspn (S (9), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + /* Similarly, strspn must traverse all characters in the first argument + except when the second string is empty. */ + T (strspn (a3, S (0))); + T (strspn (a3, S (1))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strspn (a3, S (2))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strspn (a3, S (3))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strspn (a3, S (9))); /* { dg-warning "\\\[-Wstringop-overflow" } */ +} + +void test_strcspn (void) +{ + T (strcspn (S (0), a3)); + T (strcspn (S (1), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strcspn (S (2), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strcspn (S (3), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strcspn (S (9), a3)); /* { dg-warning "\\\[-Wstringop-overflow" } */ + + T (strcspn (a3, S (0))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strcspn (a3, S (1))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strcspn (a3, S (2))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strcspn (a3, S (3))); /* { dg-warning "\\\[-Wstringop-overflow" } */ + T (strcspn (a3, S (9))); /* { dg-warning "\\\[-Wstringop-overflow" } */ +} -- 2.30.2