From ef29b12cfbb4979a89b3cbadbf485a77c8fd8fce Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Sat, 14 Dec 2019 00:52:46 +0000 Subject: [PATCH] PR middle-end/91582 - missing heap overflow detection for strcpy PR middle-end/91582 - missing heap overflow detection for strcpy PR middle-end/92868 - ICE: tree check: expected integer_cst, have ssa_name gcc/ChangeLog: PR middle-end/91582 PR middle-end/92868 * builtins.c (addr_decl_size): New function. (gimple_call_alloc_size): Add arguments. (compute_objsize): Add an argument. Set *PDECL even for allocated objects. Correct checking for negative wide_int. Correct handling of negative outer offsets into unknown regions or with unknown inner offsets. Extend offsets to at most sizetype precision. Only handle constant subobject sizes. * builtins.h (gimple_call_alloc_size): Add arguments. * tree.c (component_ref_size): Always return sizetype. * tree-ssa-strlen.c (strinfo::alloc): New member. (get_addr_stridx): Add argument. (get_stridx): Use ptrdiff_t. Add argument. (new_strinfo): Set new member. (get_string_length): Handle alloca and VLA. (dump_strlen_info): Dump more state. (maybe_invalidate): Print more info. Decrease indentation. (unshare_strinfo): Set new member. (valid_builtin_call): Handle alloca and VLA. (maybe_warn_overflow): Check and set no-warning bit. Improve handling of offsets. Print allocated objects. (handle_builtin_strlen): Handle strinfo records with null lengths. (handle_builtin_strcpy): Add argument. Call maybe_warn_overflow. (is_strlen_related_p): Handle dynamically allocated objects. (get_range): Add argument. (handle_builtin_malloc): Rename... (handle_alloc): ...to this and handle all allocation functions. (handle_builtin_memset): Call maybe_warn_overflow. (count_nonzero_bytes): Handle more MEM_REF forms. (strlen_check_and_optimize_call): Call handle_alloc_call. Pass arguments to more callees. (handle_integral_assign): Add argument. Create strinfo entries for MEM_REF assignments. (check_and_optimize_stmt): Handle more MEM_REF forms. gcc/testsuite/ChangeLog: PR middle-end/91582 * c-c++-common/Wrestrict.c: Adjust expected warnings. * gcc/testsuite/c-c++-common/Wstringop-truncation-4.c: Enable more warnings. * gcc/testsuite/c-c++-common/Wstringop-truncation.c: Remove an xfail. * gcc.dg/Warray-bounds-46.c: Disable -Wstringop-overflow. * gcc.dg/Warray-bounds-47.c: Same. * gcc.dg/Warray-bounds-52.c: New test. * gcc.dg/Wstringop-overflow-27.c: New test. * gcc.dg/Wstringop-overflow-28.c: New test. * gcc.dg/Wstringop-overflow-29.c: New test. * gcc.dg/attr-alloc_size.c (test): Disable -Warray-bounds. * gcc.dg/attr-copy-2.c: Adjust expected warnings. * gcc.dg/builtin-stringop-chk-5.c: Adjust text of expected messages. * gcc.dg/strlenopt-86.c: Relax test. * gcc.target/i386/pr82002-1.c: Prune expected warnings. From-SVN: r279392 --- gcc/ChangeLog | 40 + gcc/builtins.c | 259 +++--- gcc/builtins.h | 8 +- gcc/testsuite/ChangeLog | 19 + gcc/testsuite/c-c++-common/Wrestrict.c | 10 +- .../c-c++-common/Wstringop-truncation-4.c | 18 +- .../c-c++-common/Wstringop-truncation.c | 3 +- .../g++.dg/warn/Wstringop-overflow-3.C | 18 +- gcc/testsuite/gcc.dg/Warray-bounds-46.c | 2 +- gcc/testsuite/gcc.dg/Warray-bounds-47.c | 2 +- gcc/testsuite/gcc.dg/Warray-bounds-52.c | 97 +++ gcc/testsuite/gcc.dg/Wstringop-overflow-27.c | 293 +++++++ gcc/testsuite/gcc.dg/Wstringop-overflow-28.c | 236 +++++ gcc/testsuite/gcc.dg/Wstringop-overflow-29.c | 66 ++ gcc/testsuite/gcc.dg/attr-alloc_size.c | 8 +- gcc/testsuite/gcc.dg/attr-copy-2.c | 4 +- gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c | 4 +- gcc/testsuite/gcc.dg/strlenopt-86.c | 12 +- gcc/testsuite/gcc.target/i386/pr82002-1.c | 2 + gcc/tree-ssa-strlen.c | 822 ++++++++++++------ gcc/tree-ssa-strlen.h | 4 +- gcc/tree.c | 8 +- 22 files changed, 1543 insertions(+), 392 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/Warray-bounds-52.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-27.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-28.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-29.c diff --git a/gcc/ChangeLog b/gcc/ChangeLog index cc14a0469c4..5073ffaf3b9 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,43 @@ +2019-12-13 Martin Sebor + + PR middle-end/91582 + PR middle-end/92868 + * builtins.c (addr_decl_size): New function. + (gimple_call_alloc_size): Add arguments. + (compute_objsize): Add an argument. Set *PDECL even for allocated + objects. + Correct checking for negative wide_int. + Correct handling of negative outer offsets into unknown regions + or with unknown inner offsets. + Extend offsets to at most sizetype precision. + Only handle constant subobject sizes. + * builtins.h (gimple_call_alloc_size): Add arguments. + * tree.c (component_ref_size): Always return sizetype. + * tree-ssa-strlen.c (strinfo::alloc): New member. + (get_addr_stridx): Add argument. + (get_stridx): Use ptrdiff_t. Add argument. + (new_strinfo): Set new member. + (get_string_length): Handle alloca and VLA. + (dump_strlen_info): Dump more state. + (maybe_invalidate): Print more info. Decrease indentation. + (unshare_strinfo): Set new member. + (valid_builtin_call): Handle alloca and VLA. + (maybe_warn_overflow): Check and set no-warning bit. Improve + handling of offsets. Print allocated objects. + (handle_builtin_strlen): Handle strinfo records with null lengths. + (handle_builtin_strcpy): Add argument. Call maybe_warn_overflow. + (is_strlen_related_p): Handle dynamically allocated objects. + (get_range): Add argument. + (handle_builtin_malloc): Rename... + (handle_alloc): ...to this and handle all allocation functions. + (handle_builtin_memset): Call maybe_warn_overflow. + (count_nonzero_bytes): Handle more MEM_REF forms. + (strlen_check_and_optimize_call): Call handle_alloc_call. Pass + arguments to more callees. + (handle_integral_assign): Add argument. Create strinfo entries + for MEM_REF assignments. + (check_and_optimize_stmt): Handle more MEM_REF forms. + 2019-12-13 Iain Sandoe * config/rs6000/darwin.h (DARWIN_DYLIB1_SPEC): New. diff --git a/gcc/builtins.c b/gcc/builtins.c index 4c08214ba29..3e89f2a7f26 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -48,6 +48,7 @@ along with GCC; see the file COPYING3. If not see #include "calls.h" #include "varasm.h" #include "tree-object-size.h" +#include "tree-ssa-strlen.h" #include "realmpfr.h" #include "cfgrtl.h" #include "except.h" @@ -3696,11 +3697,13 @@ check_access (tree exp, tree, tree, tree dstwrite, return true; } -/* If STMT is a call to an allocation function, returns the size - of the object allocated by the call. */ +/* If STMT is a call to an allocation function, returns the constant + size of the object allocated by the call represented as sizetype. + If nonnull, sets RNG1[] to the range of the size. */ tree -gimple_call_alloc_size (gimple *stmt) +gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, + const vr_values *rvals /* = NULL */) { if (!stmt) return NULL_TREE; @@ -3747,11 +3750,12 @@ gimple_call_alloc_size (gimple *stmt) tree size = gimple_call_arg (stmt, argidx1); - wide_int rng1[2]; - if (TREE_CODE (size) == INTEGER_CST) - rng1[0] = rng1[1] = wi::to_wide (size); - else if (TREE_CODE (size) != SSA_NAME - || get_range_info (size, rng1, rng1 + 1) != VR_RANGE) + wide_int rng1_buf[2]; + /* If RNG1 is not set, use the buffer. */ + if (!rng1) + rng1 = rng1_buf; + + if (!get_range (size, rng1, rvals)) return NULL_TREE; if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST) @@ -3761,20 +3765,18 @@ gimple_call_alloc_size (gimple *stmt) of the upper bounds as a constant. Ignore anti-ranges. */ tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node; wide_int rng2[2]; - if (TREE_CODE (n) == INTEGER_CST) - rng2[0] = rng2[1] = wi::to_wide (n); - else if (TREE_CODE (n) != SSA_NAME - || get_range_info (n, rng2, rng2 + 1) != VR_RANGE) + if (!get_range (n, rng2, rvals)) return NULL_TREE; - /* Extend to the maximum precsion to avoid overflow. */ + /* Extend to the maximum precision to avoid overflow. */ const int prec = ADDR_MAX_PRECISION; rng1[0] = wide_int::from (rng1[0], prec, UNSIGNED); rng1[1] = wide_int::from (rng1[1], prec, UNSIGNED); rng2[0] = wide_int::from (rng2[0], prec, UNSIGNED); rng2[1] = wide_int::from (rng2[1], prec, UNSIGNED); - /* Return the lesser of SIZE_MAX and the product of the upper bounds. */ + /* Compute products of both bounds for the caller but return the lesser + of SIZE_MAX and the product of the upper bounds as a constant. */ rng1[0] = rng1[0] * rng2[0]; rng1[1] = rng1[1] * rng2[1]; tree size_max = TYPE_MAX_VALUE (sizetype); @@ -3787,36 +3789,76 @@ gimple_call_alloc_size (gimple *stmt) return wide_int_to_tree (sizetype, rng1[1]); } +/* Helper for compute_objsize. Returns the constant size of the DEST + if it refers to a variable or field and sets *PDECL to the DECL and + *POFF to zero. Otherwise returns null for other nodes. */ + +static tree +addr_decl_size (tree dest, tree *pdecl, tree *poff) +{ + if (TREE_CODE (dest) == ADDR_EXPR) + dest = TREE_OPERAND (dest, 0); + + if (DECL_P (dest)) + { + *pdecl = dest; + *poff = integer_zero_node; + if (tree size = DECL_SIZE_UNIT (dest)) + return TREE_CODE (size) == INTEGER_CST ? size : NULL_TREE; + } + + if (TREE_CODE (dest) == COMPONENT_REF) + { + *pdecl = TREE_OPERAND (dest, 1); + *poff = integer_zero_node; + /* Only return constant sizes for now while callers depend on it. */ + if (tree size = component_ref_size (dest)) + return TREE_CODE (size) == INTEGER_CST ? size : NULL_TREE; + } + + return NULL_TREE; +} + /* Helper to compute the size of the object referenced by the DEST expression which must have pointer type, using Object Size type - OSTYPE (only the least significant 2 bits are used). Return - an estimate of the size of the object if successful or NULL when - the size cannot be determined. When the referenced object involves - a non-constant offset in some range the returned value represents - the largest size given the smallest non-negative offset in the - range. If nonnull, set *PDECL to the decl of the referenced - subobject if it can be determined, or to null otherwise. Likewise, - when POFF is nonnull *POFF is set to the offset into *PDECL. + OSTYPE (only the least significant 2 bits are used). + Returns an estimate of the size of the object represented as + a sizetype constant if successful or NULL when the size cannot + be determined. + When the referenced object involves a non-constant offset in some + range the returned value represents the largest size given the + smallest non-negative offset in the range. + If nonnull, sets *PDECL to the decl of the referenced subobject + if it can be determined, or to null otherwise. Likewise, when + POFF is nonnull *POFF is set to the offset into *PDECL. + The function is intended for diagnostics and should not be used to influence code generation or optimization. */ tree compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, - tree *poff /* = NULL */) + tree *poff /* = NULL */, const vr_values *rvals /* = NULL */) { tree dummy_decl = NULL_TREE; if (!pdecl) pdecl = &dummy_decl; - tree dummy_off = size_zero_node; + tree dummy_off = NULL_TREE; if (!poff) poff = &dummy_off; - unsigned HOST_WIDE_INT size; - /* Only the two least significant bits are meaningful. */ ostype &= 3; + if (ostype) + /* Except for overly permissive calls to memcpy and other raw + memory functions with zero OSTYPE, detect the size from simple + DECLs first to more reliably than compute_builtin_object_size + set *PDECL and *POFF. */ + if (tree size = addr_decl_size (dest, pdecl, poff)) + return size; + + unsigned HOST_WIDE_INT size; if (compute_builtin_object_size (dest, ostype, &size, pdecl, poff)) return build_int_cst (sizetype, size); @@ -3826,8 +3868,15 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, if (is_gimple_call (stmt)) { /* If STMT is a call to an allocation function get the size - from its argument(s). */ - return gimple_call_alloc_size (stmt); + from its argument(s). If successful, also set *PDECL to + DEST for the caller to include in diagnostics. */ + if (tree size = gimple_call_alloc_size (stmt)) + { + *pdecl = dest; + *poff = integer_zero_node; + return size; + } + return NULL_TREE; } if (!is_gimple_assign (stmt)) @@ -3853,17 +3902,21 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, /* Ignore negative offsets for now. For others, use the lower bound as the most optimistic estimate of the (remaining) size. */ - if (wi::sign_mask (wioff)) + if (wi::neg_p (wioff)) ; - else if (wi::ltu_p (wioff, wisiz)) - { - *poff = size_binop (PLUS_EXPR, *poff, off); - return wide_int_to_tree (TREE_TYPE (size), - wi::sub (wisiz, wioff)); - } else { - *poff = size_binop (PLUS_EXPR, *poff, off); + if (*poff) + { + *poff = fold_convert (ptrdiff_type_node, *poff); + off = fold_convert (ptrdiff_type_node, *poff); + *poff = size_binop (PLUS_EXPR, *poff, off); + } + else + *poff = off; + if (wi::ltu_p (wioff, wisiz)) + return wide_int_to_tree (TREE_TYPE (size), + wi::sub (wisiz, wioff)); return size_zero_node; } } @@ -3875,32 +3928,29 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, enum value_range_kind rng = get_range_info (off, &min, &max); if (rng == VR_RANGE) - { - if (tree size = compute_objsize (dest, ostype, pdecl)) - { - wide_int wisiz = wi::to_wide (size); - - /* Ignore negative offsets for now. For others, - use the lower bound as the most optimistic - estimate of the (remaining)size. */ - if (wi::sign_mask (min) - || wi::sign_mask (max)) - ; - else if (wi::ltu_p (min, wisiz)) - { - *poff = size_binop (PLUS_EXPR, *poff, - wide_int_to_tree (sizetype, min)); + if (tree size = compute_objsize (dest, ostype, pdecl, poff)) + { + wide_int wisiz = wi::to_wide (size); + + /* Ignore negative offsets for now. For others, + use the lower bound as the most optimistic + estimate of the (remaining)size. */ + if (wi::neg_p (min) || wi::neg_p (max)) + ; + else + { + /* FIXME: For now, since the offset is non-constant, + clear *POFF to keep it from being "misused." + Eventually *POFF will need to become a range that + can be properly added to the outer offset if it + too is one. */ + *poff = NULL_TREE; + if (wi::ltu_p (min, wisiz)) return wide_int_to_tree (TREE_TYPE (size), wi::sub (wisiz, min)); - } - else - { - *poff = size_binop (PLUS_EXPR, *poff, - wide_int_to_tree (sizetype, min)); - return size_zero_node; - } - } - } + return size_zero_node; + } + } } } else if (code != ADDR_EXPR) @@ -3926,10 +3976,25 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, && *poff && integer_zerop (*poff)) return size_zero_node; - /* A valid offset into a declared object cannot be negative. */ - if (tree_int_cst_sgn (*poff) < 0) + /* A valid offset into a declared object cannot be negative. + A zero size with a zero "inner" offset is still zero size + regardless of the "other" offset OFF. */ + if (*poff + && ((integer_zerop (*poff) && integer_zerop (size)) + || (TREE_CODE (*poff) == INTEGER_CST + && tree_int_cst_sgn (*poff) < 0))) return size_zero_node; + wide_int offrng[2]; + if (!get_range (off, offrng, rvals)) + return NULL_TREE; + + /* Convert to the same precision to keep wide_int from "helpfully" + crashing whenever it sees other arguments. */ + const unsigned sizprec = TYPE_PRECISION (sizetype); + offrng[0] = wide_int::from (offrng[0], sizprec, SIGNED); + offrng[1] = wide_int::from (offrng[1], sizprec, SIGNED); + /* Adjust SIZE either up or down by the sum of *POFF and OFF above. */ if (TREE_CODE (dest) == ARRAY_REF) @@ -3938,29 +4003,35 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, tree eltype = TREE_TYPE (dest); tree tpsize = TYPE_SIZE_UNIT (eltype); if (tpsize && TREE_CODE (tpsize) == INTEGER_CST) - off = fold_build2 (MULT_EXPR, size_type_node, off, tpsize); + { + wide_int wsz = wi::to_wide (tpsize, offrng->get_precision ()); + offrng[0] *= wsz; + offrng[1] *= wsz; + } else return NULL_TREE; } - wide_int offrng[2]; - if (TREE_CODE (off) == INTEGER_CST) - offrng[0] = offrng[1] = wi::to_wide (off); - else if (TREE_CODE (off) == SSA_NAME) + wide_int wisize = wi::to_wide (size); + + if (!*poff) { - wide_int min, max; - enum value_range_kind rng - = get_range_info (off, offrng, offrng + 1); - if (rng != VR_RANGE) - return NULL_TREE; + /* If the "inner" offset is unknown and the "outer" offset + is either negative or less than SIZE, return the size + minus the offset. This may be overly optimistic in + the first case if the inner offset happens to be less + than the absolute value of the outer offset. */ + if (wi::neg_p (offrng[0])) + return size; + if (wi::ltu_p (offrng[0], wisize)) + return build_int_cst (sizetype, (wisize - offrng[0]).to_uhwi ()); + return size_zero_node; } - else - return NULL_TREE; /* Convert to the same precision to keep wide_int from "helpfuly" crashing whenever it sees other argumments. */ - offrng[0] = wide_int::from (offrng[0], ADDR_MAX_BITSIZE, SIGNED); - offrng[1] = wide_int::from (offrng[1], ADDR_MAX_BITSIZE, SIGNED); + offrng[0] = wide_int::from (offrng[0], sizprec, SIGNED); + offrng[1] = wide_int::from (offrng[1], sizprec, SIGNED); tree dstoff = *poff; if (integer_zerop (*poff)) @@ -3972,14 +4043,14 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, *poff = size_binop (PLUS_EXPR, *poff, off); } - if (wi::sign_mask (offrng[0]) >= 0) + if (!wi::neg_p (offrng[0])) { if (TREE_CODE (size) != INTEGER_CST) return NULL_TREE; /* Return the difference between the size and the offset or zero if the offset is greater. */ - wide_int wisize = wi::to_wide (size, ADDR_MAX_BITSIZE); + wide_int wisize = wi::to_wide (size, sizprec); if (wi::ltu_p (wisize, offrng[0])) return size_zero_node; @@ -3999,39 +4070,25 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, else return NULL_TREE; - dstoffrng[0] = wide_int::from (dstoffrng[0], ADDR_MAX_BITSIZE, SIGNED); - dstoffrng[1] = wide_int::from (dstoffrng[1], ADDR_MAX_BITSIZE, SIGNED); + dstoffrng[0] = wide_int::from (dstoffrng[0], sizprec, SIGNED); + dstoffrng[1] = wide_int::from (dstoffrng[1], sizprec, SIGNED); - wide_int declsize = wi::to_wide (size); - if (wi::sign_mask (dstoffrng[0]) > 0) - declsize += dstoffrng[0]; + if (!wi::neg_p (dstoffrng[0])) + wisize += dstoffrng[0]; offrng[1] += dstoffrng[1]; - if (wi::sign_mask (offrng[1]) < 0) + if (wi::neg_p (offrng[1])) return size_zero_node; - return wide_int_to_tree (sizetype, declsize); + return wide_int_to_tree (sizetype, wisize); } return NULL_TREE; } - if (TREE_CODE (dest) == COMPONENT_REF) - { - *pdecl = TREE_OPERAND (dest, 1); - return component_ref_size (dest); - } - - if (TREE_CODE (dest) != ADDR_EXPR) - return NULL_TREE; - - tree ref = TREE_OPERAND (dest, 0); - if (DECL_P (ref)) - { - *pdecl = ref; - if (tree size = DECL_SIZE_UNIT (ref)) - return TREE_CODE (size) == INTEGER_CST ? size : NULL_TREE; - } + /* Try simple DECLs not handled above. */ + if (tree size = addr_decl_size (dest, pdecl, poff)) + return size; tree type = TREE_TYPE (dest); if (TREE_CODE (type) == POINTER_TYPE) diff --git a/gcc/builtins.h b/gcc/builtins.h index 0fcccc12a39..2736f161b6b 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -133,8 +133,12 @@ extern tree fold_call_stmt (gcall *, bool); extern void set_builtin_user_assembler_name (tree decl, const char *asmspec); extern bool is_simple_builtin (tree); extern bool is_inexpensive_builtin (tree); -extern tree gimple_call_alloc_size (gimple *); -extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL); + +class vr_values; +tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL, + const vr_values * = NULL); +extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL, + const vr_values * = NULL); extern bool readonly_data_expr (tree exp); extern bool init_target_chars (void); diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 2a4d9a09b49..e182244b208 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,22 @@ +2019-12-13 Martin Sebor + + PR middle-end/91582 + * c-c++-common/Wrestrict.c: Adjust expected warnings. + * c-c++-common/Wstringop-truncation-4.c: Enable more + warnings. + * c-c++-common/Wstringop-truncation.c: Remove an xfail. + * gcc.dg/Warray-bounds-46.c: Disable -Wstringop-overflow. + * gcc.dg/Warray-bounds-47.c: Same. + * gcc.dg/Warray-bounds-52.c: New test. + * gcc.dg/Wstringop-overflow-27.c: New test. + * gcc.dg/Wstringop-overflow-28.c: New test. + * gcc.dg/Wstringop-overflow-29.c: New test. + * gcc.dg/attr-alloc_size.c (test): Disable -Warray-bounds. + * gcc.dg/attr-copy-2.c: Adjust expected warnings. + * gcc.dg/builtin-stringop-chk-5.c: Adjust text of expected messages. + * gcc.dg/strlenopt-86.c: Relax test. + * gcc.target/i386/pr82002-1.c: Prune expected warnings. + 2019-12-13 Roman Zhuykov PR rtl-optimization/92591 diff --git a/gcc/testsuite/c-c++-common/Wrestrict.c b/gcc/testsuite/c-c++-common/Wrestrict.c index c852b06bbd7..1903f502abd 100644 --- a/gcc/testsuite/c-c++-common/Wrestrict.c +++ b/gcc/testsuite/c-c++-common/Wrestrict.c @@ -731,10 +731,16 @@ void test_strcpy_range (void) r = SR (3, DIFF_MAX - 3); T (8, "01", a + r, a); - T (8, "012", a + r, a); /* { dg-warning "accessing 4 bytes at offsets \\\[3, \[0-9\]+] and 0 may overlap 1 byte at offset 3" "strcpy" } */ + + /* The accesses below might trigger either + -Wrestrict: accessing 4 bytes at offsets [3, \[0-9\]+] and 0 may overlap 1 byte at offset 3 + or + -Wstringop-overflow: writing 4 bytes into a region of size 0 + Either of the two is appropriate. */ + T (8, "012", a + r, a); /* { dg-warning "\\\[-Wrestrict|-Wstringop-overflow" } */ r = SR (DIFF_MAX - 2, DIFF_MAX - 1); - T (8, "012", a + r, a); /* { dg-warning "accessing 4 bytes at offsets \\\[\[0-9\]+, \[0-9\]+] and 0 overlaps" "strcpy" } */ + T (8, "012", a + r, a); /* { dg-warning "\\\[-Wrestrict|-Wstringop-overflow" } */ /* Exercise the full range of ptrdiff_t. */ r = signed_value (); diff --git a/gcc/testsuite/c-c++-common/Wstringop-truncation-4.c b/gcc/testsuite/c-c++-common/Wstringop-truncation-4.c index 15209536add..6ed6a28a7af 100644 --- a/gcc/testsuite/c-c++-common/Wstringop-truncation-4.c +++ b/gcc/testsuite/c-c++-common/Wstringop-truncation-4.c @@ -21,9 +21,13 @@ struct Arrays void test_arrays (struct Arrays *p, const char *s) { + /* Expect accesses to all three arrays to trigger the warning, + including the trailing one. The size argument is a good + enough indication that it is not being used as a "legacy" + flexible array member. */ strncpy (p->a, s, sizeof p->a); /* { dg-warning "\\\[-Wstringop-truncation" } */ strncpy ((char*)p->b, s, sizeof p->b); /* { dg-warning "\\\[-Wstringop-truncation" } */ - strncpy ((char*)p->c, s, sizeof p->c); /* { dg-bogus "\\\[-Wstringop-truncation" } */ + strncpy ((char*)p->c, s, sizeof p->c); /* { dg-warning "\\\[-Wstringop-truncation" } */ } struct Pointers @@ -49,9 +53,11 @@ struct ConstArrays void test_const_arrays (struct ConstArrays *p, const char *s) { + /* Expect accesses to all three arrays to trigger the warning, + including the trailing one. */ strncpy ((char*)p->a, s, sizeof p->a); /* { dg-warning "\\\[-Wstringop-truncation" } */ strncpy ((char*)p->b, s, sizeof p->b); /* { dg-warning "\\\[-Wstringop-truncation" } */ - strncpy ((char*)p->c, s, sizeof p->c); /* { dg-bogus "\\\[-Wstringop-truncation" } */ + strncpy ((char*)p->c, s, sizeof p->c); /* { dg-warning "\\\[-Wstringop-truncation" } */ } struct ConstPointers @@ -77,9 +83,11 @@ struct VolatileArrays void test_volatile_arrays (struct VolatileArrays *p, const char *s) { + /* Expect accesses to all three arrays to trigger the warning, + including the trailing one. */ strncpy ((char*)p->a, s, sizeof p->a); /* { dg-warning "\\\[-Wstringop-truncation" } */ strncpy ((char*)p->b, s, sizeof p->b); /* { dg-warning "\\\[-Wstringop-truncation" } */ - strncpy ((char*)p->c, s, sizeof p->c); /* { dg-bogus "\\\[-Wstringop-truncation" } */ + strncpy ((char*)p->c, s, sizeof p->c); /* { dg-warning "\\\[-Wstringop-truncation" } */ } struct VolatilePointers @@ -105,9 +113,11 @@ struct ConstVolatileArrays void test_const_volatile_arrays (struct ConstVolatileArrays *p, const char *s) { + /* Expect accesses to all three arrays to trigger the warning, + including the trailing one. */ strncpy ((char*)p->a, s, sizeof p->a); /* { dg-warning "\\\[-Wstringop-truncation" } */ strncpy ((char*)p->b, s, sizeof p->b); /* { dg-warning "\\\[-Wstringop-truncation" } */ - strncpy ((char*)p->c, s, sizeof p->c); /* { dg-bogus "\\\[-Wstringop-truncation" } */ + strncpy ((char*)p->c, s, sizeof p->c); /* { dg-warning "\\\[-Wstringop-truncation" } */ } struct ConstVolatilePointers diff --git a/gcc/testsuite/c-c++-common/Wstringop-truncation.c b/gcc/testsuite/c-c++-common/Wstringop-truncation.c index 592a9494ca4..5e43405fde8 100644 --- a/gcc/testsuite/c-c++-common/Wstringop-truncation.c +++ b/gcc/testsuite/c-c++-common/Wstringop-truncation.c @@ -300,8 +300,7 @@ void test_strncpy_array (Dest *pd, int i, const char* s) CPY (pd->a5, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */ CPY (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */ - /* The following is not yet handled. */ - CPY (pd->a5 + i, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" "member array" { xfail *-*-* } } */ + CPY (pd->a5 + i, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" "member array" } */ /* Verify that a copy that nul-terminates is not diagnosed. */ CPY (pd->a5, "1234", sizeof pd->a5); diff --git a/gcc/testsuite/g++.dg/warn/Wstringop-overflow-3.C b/gcc/testsuite/g++.dg/warn/Wstringop-overflow-3.C index db67136b5e7..da9ad6fd6a2 100644 --- a/gcc/testsuite/g++.dg/warn/Wstringop-overflow-3.C +++ b/gcc/testsuite/g++.dg/warn/Wstringop-overflow-3.C @@ -12,7 +12,7 @@ void sink (void*); struct Ax { char n; - char a[]; // { dg-message "at offset \[0-2\] to object 'Ax::a' declared here" } + char a[]; // { dg-message "at offset \[0-2\] to object 'Ax::a' declared here" "note: flexarray" } }; // Verify warning for a definition with no initializer. @@ -93,7 +93,7 @@ NOIPA void gaxx () struct A0 { char n; - char a[0]; // { dg-message "at offset \[0-2\] to object 'A0::a' with size 0 declared here" } + char a[0]; // { dg-message "at offset \[0-2\] to object 'A0::a' with size 0 declared here" "note: trailing zero-length array" } }; // Verify warning for a definition with no initializer. @@ -160,7 +160,7 @@ NOIPA void ga0x () struct A1 { char n; - char a[1]; // { dg-message "at offset \[1-9\] to object 'A1::a' with size 1 declared here" } + char a[1]; // { dg-message "at offset \[1-9\] to object 'A1::a' with size 1 declared here" "note: trailing one-element array" } }; // Verify warning for a definition with no initializer. @@ -234,7 +234,7 @@ NOIPA void ga1x () struct A1i { char n; - char a[1]; // { dg-message "at offset \[1-9\] to object 'A1i::a' with size 1 declared here" } + char a[1]; // { dg-message "at offset \[1-9\] to object 'A1i::a' with size 1 declared here" "note: interior one-element array" } char x; }; @@ -307,7 +307,7 @@ NOIPA void ga1ix () struct Bx { char n; - char a[]; // { dg-message "at offset 0 to object 'Bx::a' declared here" } + char a[]; // { dg-message "at offset 0 to object 'Bx::a' declared here" "note: flexarray class member" } // Verify the warning for a constant. Bx () { a[0] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } @@ -332,7 +332,7 @@ NOIPA void gbxi (int i) struct B0 { char n; - char a[0]; // { dg-message "at offset 0 to object 'B0::a' with size 0 declared here" } + char a[0]; // { dg-message "at offset 0 to object 'B0::a' with size 0 declared here" "note: zero-length trailing array class member" } B0 () { a[0] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } }; @@ -348,7 +348,7 @@ NOIPA void gb0 (void) struct B1 { char n; - char a[1]; // { dg-message "at offset 1 to object 'B1::a' with size 1 declared here" } + char a[1]; // { dg-message "at offset 1 to object 'B1::a' with size 1 declared here" "note: one-element trailing array class member" } B1 () { a[1] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } }; @@ -362,7 +362,7 @@ NOIPA void gb1 (void) struct B123 { - char a[123]; // { dg-message "at offset 123 to object 'B123::a' with size 123 declared here" } + char a[123]; // { dg-message "at offset 123 to object 'B123::a' with size 123 declared here" "note: large trailing array class member" } B123 () { a[123] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } }; @@ -376,7 +376,7 @@ NOIPA void gb123 (void) struct B234 { - char a[234]; // { dg-message "at offset 234 to object 'B234::a' with size 234 declared here" } + char a[234]; // { dg-message "at offset 234 to object 'B234::a' with size 234 declared here" "note: large trailing array class member" } B234 (int i) { a[i] = 0; } // { dg-warning "\\\[-Wstringop-overflow" } }; diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-46.c b/gcc/testsuite/gcc.dg/Warray-bounds-46.c index 4980f93a470..74e78cbdbe8 100644 --- a/gcc/testsuite/gcc.dg/Warray-bounds-46.c +++ b/gcc/testsuite/gcc.dg/Warray-bounds-46.c @@ -3,7 +3,7 @@ Test to verify that past-the-end accesses by string functions to member arrays by-reference objects are diagnosed. { dg-do compile } - { dg-options "-O2 -Wall -Wno-unused-local-typedefs -ftrack-macro-expansion=0" } */ + { dg-options "-O2 -Wall -Wno-unused-local-typedefs -Wno-stringop-overflow -ftrack-macro-expansion=0" } */ #define SA(expr) typedef int StaticAssert [2 * !!(expr) - 1] diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-47.c b/gcc/testsuite/gcc.dg/Warray-bounds-47.c index 06ad488d1e0..848ef365163 100644 --- a/gcc/testsuite/gcc.dg/Warray-bounds-47.c +++ b/gcc/testsuite/gcc.dg/Warray-bounds-47.c @@ -1,7 +1,7 @@ /* PR middle-end/91830 - Bogus -Warray-bounds on strcpy into a member of a subobject compiling binutils { dg-do compile } - { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */ + { dg-options "-O2 -Wall -Wno-stringop-overflow -ftrack-macro-expansion=0" } */ extern char* strcpy (char*, const char*); extern void sink (void*); diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-52.c b/gcc/testsuite/gcc.dg/Warray-bounds-52.c new file mode 100644 index 00000000000..1a7d76fcc2a --- /dev/null +++ b/gcc/testsuite/gcc.dg/Warray-bounds-52.c @@ -0,0 +1,97 @@ +/* PR middle-end/92341 - missing -Warray-bounds indexing past the end + of a compound literal + { dg-do compile } + { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */ + +#include "range.h" + +#define INT_MAX __INT_MAX__ +#define INT_MIN (-__INT_MAX__ - 1) + +void sink (int, ...); + + +#define T(...) sink (__LINE__, (__VA_ARGS__)) + + +void direct_idx_cst (void) +{ + T ((int[]){ }[-1]); // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[0]'" } + T ((int[]){ }[0]); // { dg-warning "array subscript 0 is outside array bounds of 'int\\\[0]'" } + T ((int[]){ }[1]); // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[0]'" } + + T ((int[]){ 1 }[-1]); // { dg-warning "array subscript -1 is below array bounds of 'int\\\[1]'" } + T ((int[]){ 1 }[0]); + T ((int[]){ 1 }[1]); // { dg-warning "array subscript 1 is above array bounds of 'int\\\[1]'" } + T ((int[]){ 1 }[INT_MIN]); // { dg-warning "array subscript -\[0-9\]+ is below array bounds of 'int\\\[1]'" } + T ((int[]){ 1 }[INT_MAX]); // { dg-warning "array subscript \[0-9\]+ is above array bounds of 'int\\\[1]'" } + T ((int[]){ 1 }[SIZE_MAX]); // { dg-warning "array subscript \[0-9\]+ is above array bounds of 'int\\\[1]'" } +} + + +void direct_idx_var (int i) +{ + T ((char[]){ }[i]); // { dg-warning "array subscript i is outside array bounds of 'char\\\[0]'" } + T ((int[]){ }[i]); // { dg-warning "array subscript i is outside array bounds of 'int\\\[0]'" } +} + + +void direct_idx_range (void) +{ + ptrdiff_t i = SR (-2, -1); + + T ((int[]){ 1 }[i]); // { dg-warning "array subscript \[ \n\r]+ is outside array bounds of 'int\\\[0]'" "pr?????" { xfail *-*-* } } +} + + +#undef T +#define T(idx, ...) do { \ + int *p = (__VA_ARGS__); \ + sink (p[idx]); \ + } while (0) + +void ptr_idx_cst (void) +{ + T (-1, (int[]){ }); // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[0]'" } + T ( 0, (int[]){ }); // { dg-warning "array subscript 0 is outside array bounds of 'int\\\[0]'" } + T (+1, (int[]){ }); // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[0]'" } + + T (-1, (int[]){ 1 }); // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[1]'" } + T ( 0, (int[]){ 1 }); + T (+1, (int[]){ 1 }); // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[1]'" } + T (INT_MIN, (int[]){ 1 }); // { dg-warning "array subscript -\[0-9\]+ is outside array bounds of 'int\\\[1]'" "lp64" { xfail ilp32 } } + T (INT_MAX, (int[]){ 1 }); // { dg-warning "array subscript \[0-9\]+ is outside array bounds of 'int\\\[1]'" "lp64" { target lp64 } } + // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[1]'" "ilp32" { target ilp32 } .-1 } + T (SIZE_MAX, (int[]){ 1 }); // { dg-warning "array subscript -?\[0-9\]+ is outside array bounds of 'int\\\[1]'" } +} + + +void ptr_idx_var (int i) +{ + T (i, (int[]){ }); // { dg-warning "array subscript \[^\n\r\]+ is outside array bounds of 'int\\\[0]'" } + T (i, (int[]){ 1 }); + T (i, (int[]){ i, 1 }); +} + +void ptr_idx_range (void) +{ + ptrdiff_t i = SR (-2, -1); + + T (i, (int[]){ }); // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[0]'" } + T (i, (int[]){ 1 }); // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[1]'" } + T (i, (int[]){ i }); // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[1]'" } + + i = SR (0, 1); + + T (i, (int[]){ }); // { dg-warning "array subscript \\\[0, 1] is outside array bounds of 'int\\\[0]'" } + T (i, (int[]){ 1 }); + + i = SR (1, 2); + T (i, (int[]){ 1 }); // { dg-warning "array subscript \\\[1, 2] is outside array bounds of 'int\\\[1]'" } + + i = SR (2, 3); + T (i, (int[]){ 1, 2, 3 }); + + i = SR (3, 4); + T (i, (int[]){ 2, 3, 4 }); // { dg-warning "array subscript \\\[3, 4] is outside array bounds of 'int\\\[3]'" } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c new file mode 100644 index 00000000000..249ce2b6ad5 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c @@ -0,0 +1,293 @@ +/* PR middle-end/91582 - missing heap overflow detection for strcpy + PR middle-end/85484 - missing -Wstringop-overflow for strcpy with + a string of non-const length + { dg-do compile } + { dg-options "-O2 -Wall -Wno-array-bounds" } */ + +typedef __SIZE_TYPE__ size_t; + +extern void* calloc (size_t, size_t); +extern void* malloc (size_t); +extern void* memcpy (void*, const void*, size_t); +extern void* memset (void*, int, size_t); +extern char* strcpy (char*, const char*); +extern size_t strlen (const char*); + +void sink (void*); + + +void test_memcpy_nowarn (const void *s, int i, size_t n) +{ + sink (memcpy (calloc (1, 1), s, 1)); + sink (memcpy (calloc (1, 2), s, 1)); + sink (memcpy (calloc (2, 1), s, 1)); + sink (memcpy (calloc (3, 1), s, 2)); + sink (memcpy (calloc (3, 1), "12", 2)); + sink (memcpy (calloc (3, 1), s, 3)); + sink (memcpy (calloc (3, 1), "12", 3)); + sink (memcpy (calloc (i, 1), s, 1)); + sink (memcpy (calloc (n, 1), s, 1)); + sink (memcpy (calloc (1, n), "", 1)); + sink (memcpy (calloc (1, i), "", 1)); + sink (memcpy (calloc (i, 1), "123", 3)); + sink (memcpy (calloc (n, 1), "123", 3)); + sink (memcpy (calloc (1, i), "123456", 7)); + sink (memcpy (calloc (1, n), "123456", 7)); + sink (memcpy (calloc (n, 1), s, 12345)); + sink (memcpy (calloc (1, n), s, n - 1)); + sink (memcpy (calloc (n, 1), s, n)); + + sink (memcpy ((char*)calloc (1, 1) + i, "123", 1)); + sink (memcpy ((char*)calloc (n, 1) + i, "123", n)); + + sink (memcpy ((char*)calloc (1, 1) + i, s, 1)); + sink (memcpy ((char*)calloc (n, 1) + i, s, n)); + + sink (memcpy (malloc (1), s, 1)); + sink (memcpy (malloc (2), s, 1)); + sink (memcpy (malloc (3), s, 2)); + sink (memcpy (malloc (3), "12", 2)); + sink (memcpy (malloc (3), s, 3)); + sink (memcpy (malloc (3), "12", 3)); + sink (memcpy (malloc (n), s, 1)); + sink (memcpy (malloc (n), "", 1)); + sink (memcpy (malloc (n), "123", 3)); + sink (memcpy (malloc (n), "123456", 7)); + sink (memcpy (malloc (n), s, 12345)); + sink (memcpy (malloc (n), s, n - 1)); + sink (memcpy (malloc (n), s, n)); + + { + const int a[] = { 1, 2, 3, 4 }; + void *p = (char*)malloc (sizeof a); + memcpy (p, a, sizeof a); + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4, 5 }; + size_t nelts = sizeof a / sizeof *a; + int vla[nelts]; + memcpy (vla, a, nelts * sizeof *vla); + sink (vla); + } +} + + +void test_memcpy_warn (const int *s, size_t n) +{ + { + void *p = (char*)malloc (0); + memcpy (p, s, 1); // { dg-warning "writing 1 byte into a region of size 0" } + sink (p); + } + + { + void *p = (char*)malloc (1); + memcpy (p, s, 2); // { dg-warning "writing 2 bytes into a region of size 1" } + sink (p); + } + + { + void *p = (char*)malloc (2); + memcpy (p, s, 3); // { dg-warning "writing 3 bytes into a region of size 2" } + sink (p); + } + + { + void *p = (char*)malloc (3); + memcpy (p, s, 4); // { dg-warning "writing 4 bytes into a region of size 3" } + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4 }; + void *p = (char*)malloc (sizeof *a); + memcpy (p, a, sizeof a); // { dg-warning "" } + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4, 5 }; + size_t nelts = sizeof a / sizeof *a; + char vla[nelts]; + memcpy (vla, a, nelts * sizeof *a); // { dg-warning "" } + sink (vla); + } + + { + void *p = malloc (n); + memcpy (p, s, n * sizeof *s); // { dg-warning "\\\[-Wstringop-overflow" "" { xfail *-*-* } } + sink (p); + } +} + +void test_memset_nowarn (int x, size_t n) +{ + sink (memset (calloc (1, 1), x, 1)); + sink (memset (calloc (1, 2), x, 1)); + sink (memset (calloc (2, 1), x, 1)); + sink (memset (calloc (3, 1), x, 2)); + sink (memset (calloc (3, 1), x, 3)); + sink (memset (calloc (n, 1), x, 1)); + sink (memset (calloc (n, 1), x, 12345)); + sink (memset (calloc (1, n), x, n - 1)); + sink (memset (calloc (n, 1), x, n)); + + sink (memset (malloc (1), x, 1)); + sink (memset (malloc (2), x, 1)); + sink (memset (malloc (3), x, 2)); + sink (memset (malloc (3), x, 3)); + sink (memset (malloc (n), x, 1)); + sink (memset (malloc (n), x, 12345)); + sink (memset (malloc (n), x, n - 1)); + sink (memset (malloc (n), x, n)); + + { + const int a[] = { 1, 2, 3, 4 }; + void *p = (char*)malloc (sizeof a); + memset (p, x, sizeof a); + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4, 5 }; + size_t nelts = sizeof a / sizeof *a; + int vla[nelts]; + memset (vla, x, nelts * sizeof *vla); + sink (vla); + } +} + + +void test_memset_warn (int x, size_t n) +{ + { + void *p = (char*)malloc (0); + memset (p, x, 1); // { dg-warning "writing 1 byte into a region of size 0" } + sink (p); + } + + { + void *p = (char*)malloc (1); + memset (p, x, 2); // { dg-warning "writing 2 bytes into a region of size 1" } + sink (p); + } + + { + void *p = (char*)malloc (2); + memset (p, x, 3); // { dg-warning "writing 3 bytes into a region of size 2" } + sink (p); + } + + { + void *p = (char*)malloc (3); + memset (p, x, 4); // { dg-warning "writing 4 bytes into a region of size 3" } + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4 }; + void *p = (char*)malloc (sizeof *a); + memset (p, 0, sizeof a); // { dg-warning "" } + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4, 5 }; + size_t nelts = sizeof a / sizeof *a; + char vla[nelts]; + memset (vla, 0, nelts * sizeof *a); // { dg-warning "" } + sink (vla); + } + + { + void *p = malloc (n); + memset (p, x, n * sizeof (int)); // { dg-warning "\\\[-Wstringop-overflow" "" { xfail *-*-* } } + sink (p); + } +} + + +void test_strcpy_nowarn (const char *s) +{ + { + const char a[] = "12"; + int n = strlen (a); + char *t = (char*)calloc (2, n); + strcpy (t, a); + sink (t); + } + + { + const char a[] = "123"; + unsigned n = strlen (a) + 1; + char *t = (char*)calloc (n, 1); + strcpy (t, a); + sink (t); + } + + { + const char a[] = "1234"; + size_t n = strlen (a) * 2; + char *t = (char*)malloc (n); + strcpy (t, a); + sink (t); + } + + { + const char a[] = "1234"; + size_t len = strlen (a) + 1; + char vla[len]; + strcpy (vla, a); + sink (vla); + } + + { + size_t n = strlen (s) + 1; + char *t = (char*)malloc (n); + strcpy (t, s); + sink (t); + } +} + + +void test_strcpy_warn (const char *s) +{ + { + const char a[] = "123"; + /* Verify that using signed int for the strlen result works (i.e., + that the conversion from signed int to size_t doesn't prevent + the detection. */ + int n = strlen (a); + char *t = (char*)calloc (n, 1); // { dg-message "at offset 0 to an object with size 3 allocated by 'calloc' here" "calloc note" { xfail *-*-* } } + // { dg-message "at offset 0 to an object with size at most 3 allocated by 'calloc' here" "calloc note" { target *-*-* } .-1 } + strcpy (t, a); // { dg-warning "writing 4 bytes into a region of size (between 0 and )?3 " } + + sink (t); + } + + { + const char a[] = "1234"; + size_t n = strlen (a); + char *t = (char*)malloc (n); // { dg-message "at offset 0 to an object with size 4 allocated by 'malloc' here" "malloc note" { xfail *-*-* } } + // { dg-message "at offset 0 to an object with size at most 4 allocated by 'malloc' here" "malloc note" { target *-*-* } .-1 } + strcpy (t, a); // { dg-warning "writing 5 bytes into a region of size (between 0 and )?4 " } + sink (t); + } + + // Exercise PR middle-end/85484. + { + size_t len = strlen (s); + char vla[len]; // { dg-message "at offset 0 to an object declared here" "vla note" } + strcpy (vla, s); // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" } + sink (vla); + } + + { + size_t n = strlen (s); + char *t = (char*)malloc (n); // { dg-message "at offset 0 to an object allocated by 'malloc' here" "malloc note" } + strcpy (t, s); // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" } + sink (t); + } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c new file mode 100644 index 00000000000..8844b9f5e71 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c @@ -0,0 +1,236 @@ +/* PR middle-end/91582 - missing heap overflow detection for strcpy + { dg-do compile } + { dg-options "-O2 -Wall -Wno-array-bounds -ftrack-macro-expansion=0" } */ + +#include "range.h" + +#define INT_MAX __INT_MAX__ +#define INT_MIN (-INT_MAX - 1) + +#define ATTR(...) __attribute__ ((__VA_ARGS__)) +#define NOIPA ATTR (noipa) + +extern void* alloca (size_t); +extern void* calloc (size_t, size_t); +extern void* malloc (size_t); + +extern ATTR (alloc_size (1), malloc) char* alloc1 (size_t); +extern ATTR (alloc_size (1, 2), malloc) char* alloc2 (size_t, size_t); + +extern char* strcpy (char*, const char*); + +void sink (void*, ...); + + +/* Verify warning in stores to an object of variable size N in a known + range, at an offset (N + I) with a constant I. */ + +void same_size_and_offset_idx_cst (void) +{ +#define T(size, off, idx) do { \ + size_t n_ = size; \ + ptrdiff_t i_ = idx; \ + char *p_ = alloc1 (n_); \ + p_ += off; \ + p_[i_] = 0; \ + sink (p_); \ + } while (0) + + { + const size_t n = UR (2, 3); + + T (n, n, -4); // { dg-warning "writing 1 byte into a region of size 0" } + // { dg-message "at offset \\\[-2, -1] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + T (n, n, -3); + T (n, n, -2); + T (n, n, -1); + T (n, n, 0); + T (n, n, 1); // { dg-warning "writing 1 byte into a region of size 0" } + // { dg-message "at offset \\\[3, 4] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + } + + { + const size_t n = UR (3, 4); + + T (n, n, -5); // { dg-warning "writing 1 byte into a region of size 0" } + // { dg-message "at offset \\\[-2, -1] to an object with size between 3 and 4 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + T (n, n, -4); + T (n, n, -3); + T (n, n, -2); + T (n, n, -1); + T (n, n, 0); + T (n, n, 1); // { dg-warning "writing 1 byte into a region of size 0" } + // { dg-message "at offset \\\[4, 5] to an object with size between 3 and 4 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + } + + { + const size_t n = UR (5, SIZE_MAX - 2); + T (n, n, -1); + T (n, n, -1); + T (n, n, -1); + T (n, n, -1); + } +} + + +/* Verify warning in stores to an object of variable size N in a known + range, at an offset (M + I) with a variable M in some range and + constant I. */ + +void different_size_and_offset_idx_cst (void) +{ + { + const size_t n = UR (2, 3); + const size_t i = UR (1, 2); + + T (n, i, -4); // { dg-warning "writing 1 byte into a region of size 0" } + // { dg-message "at offset \\\[-3, -2] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + T (n, i, -3); // { dg-warning "writing 1 byte into a region of size 0" } + // { dg-message "at offset \\\[-2, -1] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + T (n, i, -2); + T (n, i, -1); + T (n, i, 0); + T (n, i, 1); + T (n, i, 2); // { dg-warning "writing 1 byte into a region of size 0" } + // { dg-message "at offset \\\[3, 4] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + } + + { + const size_t n = UR (3, 4); + const size_t i = UR (2, 5); + + T (n, i, -6); // { dg-warning "writing 1 byte into a region of size 0" } + // { dg-message "at offset \\\[-4, -1] to an object with size between 3 and 4 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + + /* The offsets -5 and -4 are both necessarily invalid even if the sum + (i - 5) and (i - 4) are (or could be) in bounds because they imply + that the intermediate offset (p + i) is out of bounds. */ + T (n, i, -5); // { dg-warning "" "intermediate offset" { xfail *-*-* } } + T (n, i, -4); // { dg-warning "" "intermediate offset" { xfail *-*-* } } + T (n, i, -3); + T (n, i, -2); + T (n, i, -1); + T (n, i, 0); + T (n, i, 1); + T (n, i, 2); // { dg-warning "writing 1 byte into a region of size 0" } + // { dg-message "at offset \\\[4, 7] to an object with size between 3 and 4 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + } +} + + +/* Verify warning in stores to an object of variable size N in a known + range, at an offset (M + I) with a variable M in some range and + constant I. */ +void different_size_and_offset_idx_var (void) +{ + { + const size_t n = UR (3, 4); + const size_t i = UR (1, 2); + + T (n, i, SR (DIFF_MIN, 0)); + T (n, i, SR ( -3, 0)); + T (n, i, SR ( -1, 0)); + T (n, i, SR ( 0, 1)); + T (n, i, SR ( 1, 2)); + T (n, i, SR ( 2, 3)); + /* The warning is issued below but the offset and the size in + the note are wrong. See the FIXME in compute_objsize(). */ + T (n, i, SR ( 3, 4)); // { dg-warning "\\\[-Wstringop-overflow" } + // { dg-message "at offset 4 to an object with size between 3 and 4 allocated by 'alloc1'" "pr92940 note: offset addition" { xfail *-*-* } .-1 } + // { dg-message "at offset . to an object with size . allocated by 'alloc1'" "note: offset addition" { target *-*-* } .-2 } + } +} + + +void ptr_add_2 (int n, int i0, int i1) +{ + if (n < 1 || 2 < n) n = 2; + + if (i0 < 0 || 1 < i0) i0 = 0; + if (i1 < 1 || 2 < i1) i1 = 1; + + char *p = (char*)__builtin_malloc (n); + char *q = p; + + q += i0; + q[0] = 0; // p[0] + q += i1; + q[0] = 1; // p[1] + q[1] = 2; // p[2] // { dg-warning "\\\[-Wstringop-overflow" } + + sink (p, q); +} + +void ptr_add_3 (int n, int i0, int i1, int i2) +{ + if (n < 3 || 4 < n) n = 3; + + if (i0 < 0 || 1 < i0) i0 = 0; + if (i1 < 1 || 2 < i1) i1 = 1; + if (i2 < 2 || 3 < i2) i2 = 2; + + char *p = (char*)__builtin_malloc (n); + char *q = p; + + q += i0; + q[0] = 0; // p[0] + q += i1; + q[0] = 1; // p[1] + q[1] = 2; // p[2] + q += i2; + q[0] = 3; // p[3] + q[1] = 4; // p[4] // { dg-warning "\\\[-Wstringop-overflow" } + + sink (p, q); +} + +void ptr_add_4 (int n, int i0, int i1, int i2, int i3) +{ + if (n < 7 || 8 < n) n = 7; + + if (i0 < 0 || 1 < i0) i0 = 0; + if (i1 < 1 || 2 < i1) i1 = 1; + if (i2 < 2 || 3 < i2) i2 = 2; + if (i3 < 3 || 4 < i3) i3 = 3; + + char *p = (char*)__builtin_malloc (n); + char *q = p; + + q += i0; + q[0] = 0; // p[0] + q += i1; + q[0] = 1; // p[1] + q[1] = 2; // p[2] + q += i2; + q[0] = 3; // p[3] + q[1] = 4; // p[4] + q[2] = 5; // p[5] + q += i3; + q[0] = 6; // p[6] + q[1] = 7; // p[7] + q[2] = 8; // p[8] // { dg-warning "\\\[-Wstringop-overflow" } + + sink (p, q); +} + +void ptr_sub_from_end (int n, int i0, int i1, int i2, int i3) +{ + if (n < 1 || 2 < n) n = 2; + + char *p = (char*)__builtin_malloc (n); + char *q = p; + + // The following isn't diagnosed due to a bug/limitation. + q += n; // N=1 N=2 + q[-1] = 0; // p[0] p[1] + q[-2] = 1; // p[-1] p[0] + q[-3] = 2; // p[-2] p[-1] // { dg-warning "\\\[-Wstringop-overflow" "pr92939: negative offset from end" { xfail *-*-* } } + + /* The following isn't diagnosed because the warning doesn't recognize + the index below as necessarily having the same value as the size + argument to malloc. All it considers is the range. */ + q[0] = 2; // { dg-warning "\\\[-Wstringop-overflow" "pr92937: store just past the end" { xfail *-*-* } } + q[1] = 3; // { dg-warning "\\\[-Wstringop-overflow" } + + sink (p, q); +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-29.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-29.c new file mode 100644 index 00000000000..c011d05e89f --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-29.c @@ -0,0 +1,66 @@ +/* PR middle-end/91582 - missing heap overflow detection for strcpy + Verify calls via function pointers. + { dg-do compile } + { dg-options "-O2 -Wall -Wno-array-bounds -ftrack-macro-expansion=0" } */ + +typedef __attribute__ ((alloc_size (1))) char* allocfn_t (unsigned); + +extern allocfn_t allocfn; + +void sink (void*); + +void direct_call (void) +{ + char *q = allocfn (0); // { dg-message "at offset 0 to an object with size 0 allocated by 'allocfn'" } + q[0] = 0; // { dg-warning "\\\[-Wstringop-overflow" } + sink (q); +} + + +void local_ptr_call (void) +{ + allocfn_t *ptr = allocfn; + char *q = ptr (1); // { dg-message "at offset -1 to an object with size 1 allocated by 'allocfn'" } + q[0] = 0; + q[-1] = 0; // { dg-warning "\\\[-Wstringop-overflow" } + sink (q); +} + + +void global_ptr_call (void) +{ + extern allocfn_t *ptralloc; + + allocfn_t *ptr = ptralloc; + char *q = ptr (2); // { dg-message "at offset 3 to an object with size 2 allocated by 'ptralloc'" } + q[0] = 0; + q[1] = 1; + q[3] = 3; // { dg-warning "\\\[-Wstringop-overflow" } + sink (q); +} + +void global_ptr_array_call (void) +{ + extern allocfn_t * (arralloc[]); + + allocfn_t *ptr = arralloc[0]; + char *q = ptr (2); // { dg-message "at offset 3 to an object with size 2 allocated by 'ptr'" } + q[0] = 1; + q[1] = 2; + q[3] = 3; // { dg-warning "\\\[-Wstringop-overflow" } + sink (q); +} + + +struct S { allocfn_t *ptralloc; }; + +void member_ptr_call (struct S *p) +{ + char *q = p->ptralloc (3); // { dg-message "at offset 5 to an object with size 3 allocated by 'ptralloc' here" } + q[0] = 0; + q[1] = 1; + q[2] = 2; + q[5] = 0; // { dg-warning "\\\[-Wstringop-overflow" } + sink (q); +} + diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size.c b/gcc/testsuite/gcc.dg/attr-alloc_size.c index 7b0dc6e4535..4c0cd9a14c4 100644 --- a/gcc/testsuite/gcc.dg/attr-alloc_size.c +++ b/gcc/testsuite/gcc.dg/attr-alloc_size.c @@ -22,15 +22,15 @@ test (void) strcpy (p, "Hello"); p = malloc1 (6); strcpy (p, "Hello"); - strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */ p = malloc2 (__INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 6); strcpy (p, "World"); - strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */ p = calloc1 (2, 5); strcpy (p, "World"); - strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */ p = calloc2 (2, __INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 5); strcpy (p, "World"); - strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */ } diff --git a/gcc/testsuite/gcc.dg/attr-copy-2.c b/gcc/testsuite/gcc.dg/attr-copy-2.c index f311ca32aa6..ffc7208f4a7 100644 --- a/gcc/testsuite/gcc.dg/attr-copy-2.c +++ b/gcc/testsuite/gcc.dg/attr-copy-2.c @@ -99,7 +99,7 @@ void* xref12 (int); void* call_xref12 (void) { void *p = xref12 (3); - __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */ + __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" } */ return p; } @@ -197,7 +197,7 @@ void* falias_malloc (void); void* call_falias_malloc (void) { char *p = falias_malloc (); - __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */ + __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" } */ return p; } diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c index 320cd51fcf2..87dd6ac4e89 100644 --- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c @@ -110,7 +110,7 @@ void test_memop_warn_alloc (const void *src) struct A *a = __builtin_malloc (sizeof *a * 2); - memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" } */ + memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 " "memcpy into allocated" } */ escape (a, src); /* At -Wstringop-overflow=1 the destination is considered to be @@ -127,7 +127,7 @@ void test_memop_warn_alloc (const void *src) struct B *b = __builtin_malloc (sizeof *b * 2); - memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" "memcpy into allocated" } */ + memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 " "memcpy into allocated" } */ escape (b); /* The following idiom of clearing multiple members of a struct is diff --git a/gcc/testsuite/gcc.dg/strlenopt-86.c b/gcc/testsuite/gcc.dg/strlenopt-86.c index 3e86fa3c90a..d2029443556 100644 --- a/gcc/testsuite/gcc.dg/strlenopt-86.c +++ b/gcc/testsuite/gcc.dg/strlenopt-86.c @@ -9,11 +9,11 @@ unsigned n0, n1; void* -keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b) +keep_strlen_calloc_store_cst_memset (int i, unsigned a, unsigned b) { char *p = __builtin_calloc (a, 1); - p[1] = 'x'; + p[i] = 'x'; __builtin_memset (p, 0, b); @@ -23,11 +23,11 @@ keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b) } void* -keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b) +keep_strlen_calloc_store_var_memset (int i, int x, unsigned a, unsigned b) { char *p = __builtin_calloc (a, 1); - p[1] = x; + p[i] = x; __builtin_memset (p, 0, b); @@ -37,11 +37,11 @@ keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b) } void* -keep_strlen_calloc_store_memset_2 (int x, unsigned a, unsigned b, unsigned c) +keep_strlen_calloc_store_memset_2 (int i, int x, unsigned a, unsigned b, unsigned c) { char *p = __builtin_calloc (a, 1); - p[1] = x; + p[i] = x; __builtin_memset (p, 0, b); n0 = __builtin_strlen (p); diff --git a/gcc/testsuite/gcc.target/i386/pr82002-1.c b/gcc/testsuite/gcc.target/i386/pr82002-1.c index 86678a01992..b4d4bd3d125 100644 --- a/gcc/testsuite/gcc.target/i386/pr82002-1.c +++ b/gcc/testsuite/gcc.target/i386/pr82002-1.c @@ -10,3 +10,5 @@ b () a (c); a (c); } + +// { dg-prune-output "\\\[-Wstringop-overflow" } diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index 212ac7152bf..6ef07411e32 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -84,14 +84,20 @@ struct strinfo tree nonzero_chars; /* Any of the corresponding pointers for querying alias oracle. */ tree ptr; - /* This is used for two things: + /* STMT is used for two things: - To record the statement that should be used for delayed length computations. We maintain the invariant that all related strinfos have delayed lengths or none do. - - To record the malloc or calloc call that produced this result. */ + - To record the malloc or calloc call that produced this result + to optimize away malloc/memset sequences. STMT is reset after + a calloc-allocated object has been stored a non-zero value into. */ gimple *stmt; + /* Set to the dynamic allocation statement for the object (alloca, + calloc, malloc, or VLA). Unlike STMT, once set for a strinfo + object, ALLOC doesn't change. */ + gimple *alloc; /* Pointer to '\0' if known, if NULL, it can be computed as ptr + length. */ tree endptr; @@ -189,20 +195,21 @@ static int get_stridx_plus_constant (strinfo *, unsigned HOST_WIDE_INT, tree); static void handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *); /* Sets MINMAX to either the constant value or the range VAL is in - and returns true on success. When nonnull, uses RVALS to get - VAL's range. Otherwise uses get_range_info. */ + and returns either the constant value or VAL on success or null + when the range couldn't be determined. Uses RVALS when nonnull + to determine the range, otherwise get_range_info. */ -static bool -get_range (tree val, wide_int minmax[2], const vr_values *rvals = NULL) +tree +get_range (tree val, wide_int minmax[2], const vr_values *rvals /* = NULL */) { - if (tree_fits_uhwi_p (val)) + if (TREE_CODE (val) == INTEGER_CST) { minmax[0] = minmax[1] = wi::to_wide (val); - return true; + return val; } if (TREE_CODE (val) != SSA_NAME) - return false; + return NULL_TREE; if (rvals) { @@ -215,20 +222,20 @@ get_range (tree val, wide_int minmax[2], const vr_values *rvals = NULL) = (CONST_CAST (class vr_values *, rvals)->get_value_range (val)); value_range_kind rng = vr->kind (); if (rng != VR_RANGE || !range_int_cst_p (vr)) - return false; + return NULL_TREE; minmax[0] = wi::to_wide (vr->min ()); minmax[1] = wi::to_wide (vr->max ()); - return true; + return val; } value_range_kind rng = get_range_info (val, minmax, minmax + 1); if (rng == VR_RANGE) - return true; + return val; /* Do not handle anti-ranges and instead make use of the on-demand VRP if/when it becomes available (hopefully in GCC 11). */ - return false; + return NULL_TREE; } /* Return: @@ -320,7 +327,7 @@ get_next_strinfo (strinfo *si) /* Helper function for get_stridx. Return the strinfo index of the address of EXP, which is available in PTR if nonnull. If OFFSET_OUT, it is OK to return the index for some X <= &EXP and store &EXP - X in - *OFFSET_OUT. */ + *OFFSET_OUT. When nonnull uses RVALS to determine range information. */ static int get_addr_stridx (tree exp, tree ptr, unsigned HOST_WIDE_INT *offset_out, @@ -380,13 +387,14 @@ get_addr_stridx (tree exp, tree ptr, unsigned HOST_WIDE_INT *offset_out, to a known strinfo with an offset and OFFRNG is non-null, sets both elements of the OFFRNG array to the range of the offset and returns the index of the known strinfo. In this case the result - must not be used in for functions that modify the string. */ + must not be used in for functions that modify the string. + When nonnull, uses RVALS to determine range information. */ static int -get_stridx (tree exp, wide_int offrng[2] = NULL) +get_stridx (tree exp, wide_int offrng[2] = NULL, const vr_values *rvals = NULL) { if (offrng) - offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (sizetype)); + offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (ptrdiff_type_node)); if (TREE_CODE (exp) == SSA_NAME) { @@ -465,7 +473,7 @@ get_stridx (tree exp, wide_int offrng[2] = NULL) return the index corresponding to the SSA_NAME. Do this irrespective of the whether the offset is known. */ - if (get_range (off, offrng)) + if (get_range (off, offrng, rvals)) { /* When the offset range is known, increment it it by the constant offset computed in prior @@ -672,6 +680,7 @@ new_strinfo (tree ptr, int idx, tree nonzero_chars, bool full_string_p) si->nonzero_chars = nonzero_chars; si->ptr = ptr; si->stmt = NULL; + si->alloc = NULL; si->endptr = NULL_TREE; si->refcount = 1; si->idx = idx; @@ -838,6 +847,8 @@ get_string_length (strinfo *si) if (chainsi->nonzero_chars == NULL) set_endptr_and_length (loc, chainsi, lhs); break; + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: case BUILT_IN_MALLOC: break; /* BUILT_IN_CALLOC always has si->nonzero_chars set. */ @@ -885,45 +896,57 @@ dump_strlen_info (FILE *fp, gimple *stmt, const vr_values *rvals) fprintf (fp, ", ptr = "); print_generic_expr (fp, si->ptr); } - fprintf (fp, ", nonzero_chars = "); - print_generic_expr (fp, si->nonzero_chars); - if (TREE_CODE (si->nonzero_chars) == SSA_NAME) + + if (si->nonzero_chars) { - value_range_kind rng = VR_UNDEFINED; - wide_int min, max; - if (rvals) + fprintf (fp, ", nonzero_chars = "); + print_generic_expr (fp, si->nonzero_chars); + if (TREE_CODE (si->nonzero_chars) == SSA_NAME) { - const value_range_equiv *vr - = CONST_CAST (class vr_values *, rvals) - ->get_value_range (si->nonzero_chars); - rng = vr->kind (); - if (range_int_cst_p (vr)) + value_range_kind rng = VR_UNDEFINED; + wide_int min, max; + if (rvals) { - min = wi::to_wide (vr->min ()); - max = wi::to_wide (vr->max ()); + const value_range *vr + = CONST_CAST (class vr_values *, rvals) + ->get_value_range (si->nonzero_chars); + rng = vr->kind (); + if (range_int_cst_p (vr)) + { + min = wi::to_wide (vr->min ()); + max = wi::to_wide (vr->max ()); + } + else + rng = VR_UNDEFINED; } else - rng = VR_UNDEFINED; - } - else - rng = get_range_info (si->nonzero_chars, &min, &max); + rng = get_range_info (si->nonzero_chars, &min, &max); - if (rng == VR_RANGE || rng == VR_ANTI_RANGE) - { - fprintf (fp, " %s[%llu, %llu]", - rng == VR_RANGE ? "" : "~", - (long long) min.to_uhwi (), - (long long) max.to_uhwi ()); + if (rng == VR_RANGE || rng == VR_ANTI_RANGE) + { + fprintf (fp, " %s[%llu, %llu]", + rng == VR_RANGE ? "" : "~", + (long long) min.to_uhwi (), + (long long) max.to_uhwi ()); + } } } - fprintf (fp, " , refcount = %i", si->refcount); + + fprintf (fp, ", refcount = %i", si->refcount); if (si->stmt) { fprintf (fp, ", stmt = "); print_gimple_expr (fp, si->stmt, 0); } + if (si->alloc) + { + fprintf (fp, ", alloc = "); + print_gimple_expr (fp, si->alloc, 0); + } if (si->writable) fprintf (fp, ", writable"); + if (si->dont_invalidate) + fprintf (fp, ", dont_invalidate"); if (si->full_string_p) fprintf (fp, ", full_string_p"); if (strinfo *next = get_next_strinfo (si)) @@ -1197,80 +1220,87 @@ get_range_strlen_dynamic (tree src, c_strlen_data *pdata, BITMAP_FREE (visited); } -/* Invalidate string length information for strings whose length - might change due to stores in stmt, except those marked DON'T - INVALIDATE. For string-modifying statements, ZERO_WRITE is - set when the statement wrote only zeros. */ +/* Invalidate string length information for strings whose length might + change due to stores in STMT, except those marked DONT_INVALIDATE. + For string-modifying statements, ZERO_WRITE is set when the statement + wrote only zeros. + Returns true if any STRIDX_TO_STRINFO entries were considered + for invalidation. */ static bool maybe_invalidate (gimple *stmt, bool zero_write = false) { if (dump_file && (dump_flags & TDF_DETAILS)) - fprintf (dump_file, " %s()\n", __func__); + { + fprintf (dump_file, "%s called for ", __func__); + print_gimple_stmt (dump_file, stmt, TDF_LINENO); + } strinfo *si; - unsigned int i; bool nonempty = false; - for (i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i) - if (si != NULL) - { - if (!si->dont_invalidate) - { - ao_ref r; - tree size = NULL_TREE; - if (si->nonzero_chars) - { - /* Include the terminating nul in the size of the string - to consider when determining possible clobber. */ - tree type = TREE_TYPE (si->nonzero_chars); - size = fold_build2 (PLUS_EXPR, type, si->nonzero_chars, - build_int_cst (type, 1)); - } - ao_ref_init_from_ptr_and_size (&r, si->ptr, size); - if (stmt_may_clobber_ref_p_1 (stmt, &r)) - { - if (dump_file && (dump_flags & TDF_DETAILS)) - { - if (size && tree_fits_uhwi_p (size)) - fprintf (dump_file, - " statement may clobber string " - HOST_WIDE_INT_PRINT_UNSIGNED " long\n", - tree_to_uhwi (size)); - else - fprintf (dump_file, - " statement may clobber string\n"); - } + for (unsigned i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i) + { + if (si == NULL || !POINTER_TYPE_P (TREE_TYPE (si->ptr))) + continue; - set_strinfo (i, NULL); - free_strinfo (si); - continue; - } + nonempty = true; - if (size - && !zero_write - && si->stmt - && is_gimple_call (si->stmt) - && (DECL_FUNCTION_CODE (gimple_call_fndecl (si->stmt)) - == BUILT_IN_CALLOC)) - { - /* If the clobber test above considered the length of - the string (including the nul), then for (potentially) - non-zero writes that might modify storage allocated by - calloc consider the whole object and if it might be - clobbered by the statement reset the allocation - statement. */ - ao_ref_init_from_ptr_and_size (&r, si->ptr, NULL_TREE); - if (stmt_may_clobber_ref_p_1 (stmt, &r)) - si->stmt = NULL; - } - } - si->dont_invalidate = false; - nonempty = true; - } + /* Unconditionally reset DONT_INVALIDATE. */ + bool dont_invalidate = si->dont_invalidate; + si->dont_invalidate = false; + + if (dont_invalidate) + continue; + + ao_ref r; + tree size = NULL_TREE; + if (si->nonzero_chars) + { + /* Include the terminating nul in the size of the string + to consider when determining possible clobber. */ + tree type = TREE_TYPE (si->nonzero_chars); + size = fold_build2 (PLUS_EXPR, type, si->nonzero_chars, + build_int_cst (type, 1)); + } + ao_ref_init_from_ptr_and_size (&r, si->ptr, size); + if (stmt_may_clobber_ref_p_1 (stmt, &r)) + { + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fputs (" statement may clobber object ", dump_file); + print_generic_expr (dump_file, si->ptr); + if (size && tree_fits_uhwi_p (size)) + fprintf (dump_file, " " HOST_WIDE_INT_PRINT_UNSIGNED + " bytes in size", tree_to_uhwi (size)); + fputc ('\n', dump_file); + } + + set_strinfo (i, NULL); + free_strinfo (si); + continue; + } + + if (size + && !zero_write + && si->stmt + && is_gimple_call (si->stmt) + && (DECL_FUNCTION_CODE (gimple_call_fndecl (si->stmt)) + == BUILT_IN_CALLOC)) + { + /* If the clobber test above considered the length of + the string (including the nul), then for (potentially) + non-zero writes that might modify storage allocated by + calloc consider the whole object and if it might be + clobbered by the statement reset the statement. */ + ao_ref_init_from_ptr_and_size (&r, si->ptr, NULL_TREE); + if (stmt_may_clobber_ref_p_1 (stmt, &r)) + si->stmt = NULL; + } + } if (dump_file && (dump_flags & TDF_DETAILS)) - fprintf (dump_file, " %s() ==> %i\n", __func__, nonempty); + fprintf (dump_file, "%s returns %i\n", __func__, nonempty); return nonempty; } @@ -1289,6 +1319,7 @@ unshare_strinfo (strinfo *si) nsi = new_strinfo (si->ptr, si->idx, si->nonzero_chars, si->full_string_p); nsi->stmt = si->stmt; + nsi->alloc = si->alloc; nsi->endptr = si->endptr; nsi->first = si->first; nsi->prev = si->prev; @@ -1582,6 +1613,8 @@ valid_builtin_call (gimple *stmt) return false; break; + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: case BUILT_IN_CALLOC: case BUILT_IN_MALLOC: case BUILT_IN_MEMCPY: @@ -1858,92 +1891,159 @@ maybe_set_strlen_range (tree lhs, tree src, tree bound) } /* Diagnose buffer overflow by a STMT writing LEN + PLUS_ONE bytes, - into an object designated by the LHS of STMT otherise. */ + either into a region allocated for the object SI when non-null, + or into an object designated by the LHS of STMT otherwise. + When nonnull uses RVALS to determine range information. + RAWMEM may be set by memcpy and other raw memory functions + to allow accesses across subobject boundaries. */ static void maybe_warn_overflow (gimple *stmt, tree len, const vr_values *rvals = NULL, - strinfo *si = NULL, bool plus_one = false) + strinfo *si = NULL, bool plus_one = false, + bool rawmem = false) { if (!len || gimple_no_warning_p (stmt)) return; + /* The DECL of the function performing the write if it is done + by one. */ tree writefn = NULL_TREE; - tree destdecl = NULL_TREE; - tree destsize = NULL_TREE; + /* The destination expression involved in the store STMT. */ tree dest = NULL_TREE; - /* The offset into the destination object set by compute_objsize - but already reflected in DESTSIZE. */ - tree destoff = NULL_TREE; - if (is_gimple_assign (stmt)) - { - dest = gimple_assign_lhs (stmt); - if (TREE_NO_WARNING (dest)) - return; - - /* For assignments try to determine the size of the destination - first. Set DESTOFF to the the offset on success. */ - tree off = size_zero_node; - destsize = compute_objsize (dest, 1, &destdecl, &off); - if (destsize) - destoff = off; - } + dest = gimple_assign_lhs (stmt); else if (is_gimple_call (stmt)) { - writefn = gimple_call_fndecl (stmt); dest = gimple_call_arg (stmt, 0); + writefn = gimple_call_fndecl (stmt); } + if (TREE_NO_WARNING (dest)) + return; + /* The offset into the destination object computed below and not - reflected in DESTSIZE. Either DESTOFF is set above or OFFRNG - below. */ + reflected in DESTSIZE. */ wide_int offrng[2]; - offrng[0] = wi::zero (TYPE_PRECISION (sizetype)); - offrng[1] = offrng[0]; + const int off_prec = TYPE_PRECISION (ptrdiff_type_node); + offrng[0] = offrng[1] = wi::zero (off_prec); - if (!destsize && !si && dest) + if (!si) { - /* For both assignments and calls, if no destination STRINFO was - provided, try to get it from the DEST. */ + /* If no destination STRINFO was provided try to get it from + the DEST argument. */ tree ref = dest; - tree off = NULL_TREE; if (TREE_CODE (ref) == ARRAY_REF) { /* Handle stores to VLAs (represented as ARRAY_REF (MEM_REF (vlaptr, 0), N]. */ - off = TREE_OPERAND (ref, 1); + tree off = TREE_OPERAND (ref, 1); ref = TREE_OPERAND (ref, 0); + if (get_range (off, offrng, rvals)) + { + offrng[0] = offrng[0].from (offrng[0], off_prec, SIGNED); + offrng[1] = offrng[1].from (offrng[1], off_prec, SIGNED); + } + else + { + offrng[0] = wi::to_wide (TYPE_MIN_VALUE (ptrdiff_type_node)); + offrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)); + } } if (TREE_CODE (ref) == MEM_REF) { tree mem_off = TREE_OPERAND (ref, 1); - if (off) + ref = TREE_OPERAND (ref, 0); + wide_int memoffrng[2]; + if (get_range (mem_off, memoffrng, rvals)) { - if (!integer_zerop (mem_off)) - return; + offrng[0] += memoffrng[0]; + offrng[1] += memoffrng[1]; } else - off = mem_off; - ref = TREE_OPERAND (ref, 0); + { + offrng[0] = wi::to_wide (TYPE_MIN_VALUE (ptrdiff_type_node)); + offrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)); + } } - if (int idx = get_stridx (ref, offrng)) + wide_int stroffrng[2]; + if (int idx = get_stridx (ref, stroffrng, rvals)) { si = get_strinfo (idx); - if (off && TREE_CODE (off) == INTEGER_CST) + offrng[0] += stroffrng[0]; + offrng[1] += stroffrng[1]; + } + } + + /* The allocation call if the destination object was allocated + by one. */ + gimple *alloc_call = NULL; + /* The DECL of the destination object if known and not dynamically + allocated. */ + tree destdecl = NULL_TREE; + /* The offset into the destination object set by compute_objsize + but already reflected in DESTSIZE. */ + tree destoff = NULL_TREE; + /* The size of the destination region (which is smaller than + the destination object for stores at a non-zero offset). */ + tree destsize = NULL_TREE; + + /* Compute the range of sizes of the destination object. The range + is constant for declared objects but may be a range for allocated + objects. */ + const int siz_prec = TYPE_PRECISION (size_type_node); + wide_int sizrng[2]; + if (si) + { + destsize = gimple_call_alloc_size (si->alloc, sizrng, rvals); + alloc_call = si->alloc; + } + else + offrng[0] = offrng[1] = wi::zero (off_prec); + + if (!destsize) + { + /* If there is no STRINFO for DEST, fall back on compute_objsize. */ + tree off = NULL_TREE; + destsize = compute_objsize (dest, rawmem ? 0 : 1, &destdecl, &off, rvals); + if (destsize) + { + /* Remember OFF but clear OFFRNG that may have been set above. */ + destoff = off; + offrng[0] = offrng[1] = wi::zero (off_prec); + + if (destdecl && TREE_CODE (destdecl) == SSA_NAME) { - wide_int wioff = wi::to_wide (off, offrng->get_precision ()); - offrng[0] += wioff; - offrng[1] += wioff; + gimple *stmt = SSA_NAME_DEF_STMT (destdecl); + if (is_gimple_call (stmt)) + alloc_call = stmt; + destdecl = NULL_TREE; + } + + if (!get_range (destsize, sizrng, rvals)) + { + /* On failure, rather than failing, set the maximum range + so that overflow in allocated objects whose size depends + on the strlen of the source can still be diagnosed + below. */ + sizrng[0] = wi::zero (siz_prec); + sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (sizetype)); } } - else - return; } + if (!destsize) + { + sizrng[0] = wi::zero (siz_prec); + sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (sizetype)); + }; + + sizrng[0] = sizrng[0].from (sizrng[0], siz_prec, UNSIGNED); + sizrng[1] = sizrng[1].from (sizrng[1], siz_prec, UNSIGNED); + /* Return early if the DESTSIZE size expression is the same as LEN and the offset into the destination is zero. This might happen in the case of a pair of malloc and memset calls to allocate @@ -1961,37 +2061,43 @@ maybe_warn_overflow (gimple *stmt, tree len, lenrng[1] += 1; } - /* Compute the range of sizes of the destination object. The range - is constant for declared objects but may be a range for allocated - objects. */ - wide_int sizrng[2]; - if (!destsize || !get_range (destsize, sizrng, rvals)) - { - /* On failure, rather than bailing outright, use the maximum range - so that overflow in allocated objects whose size depends on - the strlen of the source can still be diagnosed below. */ - sizrng[0] = wi::zero (lenrng->get_precision ()); - sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)); - } - - /* The size of the remaining space in the destination computed as - the size of the latter minus the offset into it. */ + /* The size of the remaining space in the destination computed + as the size of the latter minus the offset into it. */ wide_int spcrng[2] = { sizrng[0], sizrng[1] }; - if (wi::sign_mask (offrng[0])) + if (wi::neg_p (offrng[0]) && wi::neg_p (offrng[1])) { - /* FIXME: Handle negative offsets into allocated objects. */ - if (destdecl) - spcrng[0] = spcrng[1] = wi::zero (spcrng->get_precision ()); - else + /* When the offset is negative and the size of the destination + object unknown there is little to do. + FIXME: Detect offsets that are necessarily invalid regardless + of the size of the object. */ + if (!destsize) return; + + /* The remaining space is necessarily zero. */ + spcrng[0] = spcrng[1] = wi::zero (spcrng->get_precision ()); + } + else if (wi::neg_p (offrng[0])) + { + /* When the lower bound of the offset is negative but the upper + bound is not, reduce the upper bound of the remaining space + by the upper bound of the offset but leave the lower bound + unchanged. If that makes the upper bound of the space less + than the lower bound swap the two. */ + spcrng[1] -= wi::ltu_p (offrng[1], spcrng[1]) ? offrng[1] : spcrng[1]; + if (wi::ltu_p (spcrng[1], spcrng[0])) + std::swap (spcrng[1], spcrng[0]); } else { + /* When the offset is positive reduce the remaining space by + the lower bound of the offset or clear it if the offset is + greater. */ spcrng[0] -= wi::ltu_p (offrng[0], spcrng[0]) ? offrng[0] : spcrng[0]; spcrng[1] -= wi::ltu_p (offrng[0], spcrng[1]) ? offrng[0] : spcrng[1]; } - if (wi::leu_p (lenrng[0], spcrng[0])) + if (wi::leu_p (lenrng[0], spcrng[0]) + && wi::leu_p (lenrng[1], spcrng[1])) return; if (lenrng[0] == spcrng[1] @@ -2092,6 +2198,8 @@ maybe_warn_overflow (gimple *stmt, tree len, if (!warned) return; + gimple_set_no_warning (stmt, true); + /* If DESTOFF is not null, use it to format the offset value/range. */ if (destoff) get_range (destoff, offrng); @@ -2117,17 +2225,91 @@ maybe_warn_overflow (gimple *stmt, tree len, offstr, destdecl); return; } + + if (!alloc_call) + return; + + tree allocfn = gimple_call_fndecl (alloc_call); + if (!allocfn) + { + /* For an ALLOC_CALL via a function pointer make a small effort + to determine the destination of the pointer. */ + allocfn = gimple_call_fn (alloc_call); + if (TREE_CODE (allocfn) == SSA_NAME) + { + gimple *def = SSA_NAME_DEF_STMT (allocfn); + if (gimple_assign_single_p (def)) + { + tree rhs = gimple_assign_rhs1 (def); + if (DECL_P (rhs)) + allocfn = rhs; + else if (TREE_CODE (rhs) == COMPONENT_REF) + allocfn = TREE_OPERAND (rhs, 1); + } + } + } + + if (gimple_call_builtin_p (alloc_call, BUILT_IN_ALLOCA_WITH_ALIGN)) + { + if (sizrng[0] == sizrng[1]) + inform (gimple_location (alloc_call), + "at offset %s to an object with size %wu declared here", + offstr, sizrng[0].to_uhwi ()); + else if (sizrng[0] == 0) + { + /* Avoid printing impossible sizes. */ + if (wi::ltu_p (sizrng[1], + wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2)) + inform (gimple_location (alloc_call), + "at offset %s to an object with size at most %wu " + "declared here", + offstr, sizrng[1].to_uhwi ()); + else + inform (gimple_location (alloc_call), + "at offset %s to an object declared here", offstr); + } + else + inform (gimple_location (alloc_call), + "at offset %s to an object with size between %wu and %wu " + "declared here", + offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi ()); + return; + } + + if (sizrng[0] == sizrng[1]) + inform (gimple_location (alloc_call), + "at offset %s to an object with size %wu allocated by %qE here", + offstr, sizrng[0].to_uhwi (), allocfn); + else if (sizrng[0] == 0) + { + /* Avoid printing impossible sizes. */ + if (wi::ltu_p (sizrng[1], + wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2)) + inform (gimple_location (alloc_call), + "at offset %s to an object with size at most %wu allocated " + "by %qD here", + offstr, sizrng[1].to_uhwi (), allocfn); + else + inform (gimple_location (alloc_call), + "at offset %s to an object allocated by %qE here", + offstr, allocfn); + } + else + inform (gimple_location (alloc_call), + "at offset %s to an object with size between %wu and %wu " + "allocated by %qE here", + offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi (), allocfn); } /* Convenience wrapper for the above. */ static inline void maybe_warn_overflow (gimple *stmt, unsigned HOST_WIDE_INT len, - const vr_values *rvals = NULL, - strinfo *si = NULL, bool plus_one = false) + const vr_values *rvals = NULL, strinfo *si = NULL, + bool plus_one = false, bool rawmem = false) { maybe_warn_overflow (stmt, build_int_cst (size_type_node, len), rvals, - si, plus_one); + si, plus_one, rawmem); } /* Handle a strlen call. If strlen of the argument is known, replace @@ -2243,7 +2425,7 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi) tree old = si->nonzero_chars; si->nonzero_chars = lhs; si->full_string_p = true; - if (TREE_CODE (old) == INTEGER_CST) + if (old && TREE_CODE (old) == INTEGER_CST) { old = fold_convert_loc (loc, TREE_TYPE (lhs), old); tree adj = fold_build2_loc (loc, MINUS_EXPR, @@ -2422,10 +2604,11 @@ handle_builtin_strchr (gimple_stmt_iterator *gsi) /* Handle a strcpy-like ({st{r,p}cpy,__st{r,p}cpy_chk}) call. If strlen of the second argument is known, strlen of the first argument is the same after this call. Furthermore, attempt to convert it to - memcpy. */ + memcpy. Uses RVALS to determine range information. */ static void -handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi) +handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi, + const vr_values *rvals) { int idx, didx; tree src, dst, srclen, len, lhs, type, fn, oldlen; @@ -2459,6 +2642,11 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi) else if (idx < 0) srclen = build_int_cst (size_type_node, ~idx); + maybe_warn_overflow (stmt, srclen, rvals, olddsi, true); + + if (olddsi != NULL) + adjust_last_stmt (olddsi, stmt, false); + loc = gimple_location (stmt); if (srclen == NULL_TREE) switch (bcode) @@ -2709,26 +2897,58 @@ is_strlen_related_p (tree src, tree len) if (TREE_CODE (len) != SSA_NAME) return false; - gimple *def_stmt = SSA_NAME_DEF_STMT (len); - if (!def_stmt) + if (TREE_CODE (src) == SSA_NAME) + { + gimple *srcdef = SSA_NAME_DEF_STMT (src); + if (is_gimple_assign (srcdef)) + { + /* Handle bitwise AND used in conversions from wider size_t + to narrower unsigned types. */ + tree_code code = gimple_assign_rhs_code (srcdef); + if (code == BIT_AND_EXPR + || code == NOP_EXPR) + return is_strlen_related_p (gimple_assign_rhs1 (srcdef), len); + + return false; + } + + if (gimple_call_builtin_p (srcdef, BUILT_IN_NORMAL)) + { + /* If SRC is the result of a call to an allocation function + or strlen, use the function's argument instead. */ + tree func = gimple_call_fndecl (srcdef); + built_in_function code = DECL_FUNCTION_CODE (func); + if (code == BUILT_IN_ALLOCA + || code == BUILT_IN_ALLOCA_WITH_ALIGN + || code == BUILT_IN_MALLOC + || code == BUILT_IN_STRLEN) + return is_strlen_related_p (gimple_call_arg (srcdef, 0), len); + + /* FIXME: Handle other functions with attribute alloc_size. */ + return false; + } + } + + gimple *lendef = SSA_NAME_DEF_STMT (len); + if (!lendef) return false; - if (is_gimple_call (def_stmt)) + if (is_gimple_call (lendef)) { - tree func = gimple_call_fndecl (def_stmt); - if (!valid_builtin_call (def_stmt) + tree func = gimple_call_fndecl (lendef); + if (!valid_builtin_call (lendef) || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN) return false; - tree arg = gimple_call_arg (def_stmt, 0); + tree arg = gimple_call_arg (lendef, 0); return is_strlen_related_p (src, arg); } - if (!is_gimple_assign (def_stmt)) + if (!is_gimple_assign (lendef)) return false; - tree_code code = gimple_assign_rhs_code (def_stmt); - tree rhs1 = gimple_assign_rhs1 (def_stmt); + tree_code code = gimple_assign_rhs_code (lendef); + tree rhs1 = gimple_assign_rhs1 (lendef); tree rhstype = TREE_TYPE (rhs1); if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR) @@ -2741,7 +2961,7 @@ is_strlen_related_p (tree src, tree len) return is_strlen_related_p (src, rhs1); } - if (tree rhs2 = gimple_assign_rhs2 (def_stmt)) + if (tree rhs2 = gimple_assign_rhs2 (lendef)) { /* Integer subtraction is considered strlen-related when both arguments are integers and second one is strlen-related. */ @@ -3187,34 +3407,37 @@ handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi) /* Handle a memcpy-like ({mem{,p}cpy,__mem{,p}cpy_chk}) call. If strlen of the second argument is known and length of the third argument is that plus one, strlen of the first argument is the same after this - call. */ + call. Uses RVALS to determine range information. */ static void -handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi) +handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi, + const vr_values *rvals) { - int idx, didx; - tree src, dst, len, lhs, oldlen, newlen; + tree lhs, oldlen, newlen; gimple *stmt = gsi_stmt (*gsi); - strinfo *si, *dsi, *olddsi; + strinfo *si, *dsi; - len = gimple_call_arg (stmt, 2); - src = gimple_call_arg (stmt, 1); - dst = gimple_call_arg (stmt, 0); - idx = get_stridx (src); - if (idx == 0) - return; + tree len = gimple_call_arg (stmt, 2); + tree src = gimple_call_arg (stmt, 1); + tree dst = gimple_call_arg (stmt, 0); - didx = get_stridx (dst); - olddsi = NULL; + int didx = get_stridx (dst); + strinfo *olddsi = NULL; if (didx > 0) olddsi = get_strinfo (didx); else if (didx < 0) return; if (olddsi != NULL - && tree_fits_uhwi_p (len) && !integer_zerop (len)) - adjust_last_stmt (olddsi, stmt, false); + { + maybe_warn_overflow (stmt, len, rvals, olddsi, false, true); + adjust_last_stmt (olddsi, stmt, false); + } + + int idx = get_stridx (src); + if (idx == 0) + return; bool full_string_p; if (idx > 0) @@ -3611,10 +3834,11 @@ handle_builtin_strcat (enum built_in_function bcode, gimple_stmt_iterator *gsi) gimple_set_no_warning (stmt, true); } -/* Handle a call to malloc or calloc. */ +/* Handle a call to an allocation function like alloca, malloc or calloc, + or an ordinary allocation function declared with attribute alloc_size. */ static void -handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi) +handle_alloc_call (enum built_in_function bcode, gimple_stmt_iterator *gsi) { gimple *stmt = gsi_stmt (*gsi); tree lhs = gimple_call_lhs (stmt); @@ -3628,59 +3852,89 @@ handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi) length = build_int_cst (size_type_node, 0); strinfo *si = new_strinfo (lhs, idx, length, length != NULL_TREE); if (bcode == BUILT_IN_CALLOC) - si->endptr = lhs; + { + /* Only set STMT for calloc and malloc. */ + si->stmt = stmt; + /* Only set ENDPTR for calloc. */ + si->endptr = lhs; + } + else if (bcode == BUILT_IN_MALLOC) + si->stmt = stmt; + + /* Set ALLOC is set for all allocation functions. */ + si->alloc = stmt; set_strinfo (idx, si); si->writable = true; - si->stmt = stmt; si->dont_invalidate = true; } /* Handle a call to memset. After a call to calloc, memset(,0,) is unnecessary. memset(malloc(n),0,n) is calloc(n,1). - return true when the call is transformed, false otherwise. */ + return true when the call is transformed, false otherwise. + When nonnull uses RVALS to determine range information. */ static bool -handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write) +handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write, + const vr_values *rvals) { - gimple *stmt2 = gsi_stmt (*gsi); - if (!integer_zerop (gimple_call_arg (stmt2, 1))) - return false; - - /* Let the caller know the memset call cleared the destination. */ - *zero_write = true; - - tree ptr = gimple_call_arg (stmt2, 0); - int idx1 = get_stridx (ptr); + gimple *memset_stmt = gsi_stmt (*gsi); + tree ptr = gimple_call_arg (memset_stmt, 0); + /* Set to the non-constant offset added to PTR. */ + wide_int offrng[2]; + int idx1 = get_stridx (ptr, offrng, rvals); if (idx1 <= 0) return false; strinfo *si1 = get_strinfo (idx1); if (!si1) return false; - gimple *stmt1 = si1->stmt; - if (!stmt1 || !is_gimple_call (stmt1)) + gimple *alloc_stmt = si1->alloc; + if (!alloc_stmt || !is_gimple_call (alloc_stmt)) + return false; + tree callee1 = gimple_call_fndecl (alloc_stmt); + if (!valid_builtin_call (alloc_stmt)) + return false; + tree alloc_size = gimple_call_arg (alloc_stmt, 0); + tree memset_size = gimple_call_arg (memset_stmt, 2); + + /* Check for overflow. */ + maybe_warn_overflow (memset_stmt, memset_size, rvals, NULL, false, true); + + /* Bail when there is no statement associated with the destination + (the statement may be null even when SI1->ALLOC is not). */ + if (!si1->stmt) return false; - tree callee1 = gimple_call_fndecl (stmt1); - if (!valid_builtin_call (stmt1)) + + /* Avoid optimizing if store is at a variable offset from the beginning + of the allocated object. */ + if (offrng[0] != 0 || offrng[0] != offrng[1]) return false; + + /* Bail when the call writes a non-zero value. */ + if (!integer_zerop (gimple_call_arg (memset_stmt, 1))) + return false; + + /* Let the caller know the memset call cleared the destination. */ + *zero_write = true; + enum built_in_function code1 = DECL_FUNCTION_CODE (callee1); - tree size = gimple_call_arg (stmt2, 2); if (code1 == BUILT_IN_CALLOC) - /* Not touching stmt1 */ ; + /* Not touching alloc_stmt */ ; else if (code1 == BUILT_IN_MALLOC - && operand_equal_p (gimple_call_arg (stmt1, 0), size, 0)) + && operand_equal_p (memset_size, alloc_size, 0)) { - gimple_stmt_iterator gsi1 = gsi_for_stmt (stmt1); + /* Replace the malloc + memset calls with calloc. */ + gimple_stmt_iterator gsi1 = gsi_for_stmt (si1->stmt); update_gimple_call (&gsi1, builtin_decl_implicit (BUILT_IN_CALLOC), 2, - size, build_one_cst (size_type_node)); + alloc_size, build_one_cst (size_type_node)); si1->nonzero_chars = build_int_cst (size_type_node, 0); si1->full_string_p = true; si1->stmt = gsi_stmt (gsi1); } else return false; - tree lhs = gimple_call_lhs (stmt2); - unlink_stmt_vdef (stmt2); + tree lhs = gimple_call_lhs (memset_stmt); + unlink_stmt_vdef (memset_stmt); if (lhs) { gimple *assign = gimple_build_assign (lhs, ptr); @@ -3689,7 +3943,7 @@ handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write) else { gsi_remove (gsi, true); - release_defs (stmt2); + release_defs (memset_stmt); } return true; @@ -4391,7 +4645,8 @@ int ssa_name_limit_t::next_ssa_name (tree ssa_name) OFFSET and NBYTES are the offset into the representation and the size of the access to it determined from a MEM_REF or zero for other expressions. - Avoid recursing deeper than the limits in SNLIM allow. + Uses RVALS to determine range information. + Avoids recursing deeper than the limits in SNLIM allow. Returns true on success and false otherwise. */ static bool @@ -4438,6 +4693,29 @@ count_nonzero_bytes (tree exp, unsigned HOST_WIDE_INT offset, if (maxlen + 1 < nbytes) return false; + if (!nbytes + && TREE_CODE (si->ptr) == SSA_NAME + && !POINTER_TYPE_P (TREE_TYPE (si->ptr))) + { + /* SI->PTR is an SSA_NAME with a DEF_STMT like + _1 = MEM [(char * {ref-all})s_4(D)]; */ + gimple *stmt = SSA_NAME_DEF_STMT (exp); + if (gimple_assign_single_p (stmt) + && gimple_assign_rhs_code (stmt) == MEM_REF) + { + tree rhs = gimple_assign_rhs1 (stmt); + if (tree refsize = TYPE_SIZE_UNIT (TREE_TYPE (rhs))) + if (tree_fits_uhwi_p (refsize)) + { + nbytes = tree_to_uhwi (refsize); + maxlen = nbytes; + } + } + + if (!nbytes) + return false; + } + if (nbytes <= minlen) *nulterm = false; @@ -4454,7 +4732,7 @@ count_nonzero_bytes (tree exp, unsigned HOST_WIDE_INT offset, lenrange[1] = maxlen; if (lenrange[2] < nbytes) - (lenrange[2] = nbytes); + lenrange[2] = nbytes; /* Since only the length of the string are known and not its contents, clear ALLNUL and ALLNONNUL purely on the basis of the length. */ @@ -4672,7 +4950,8 @@ count_nonzero_bytes (tree exp, unsigned lenrange[3], bool *nulterm, the next statement in the basic block and false otherwise. */ static bool -handle_store (gimple_stmt_iterator *gsi, bool *zero_write, const vr_values *rvals) +handle_store (gimple_stmt_iterator *gsi, bool *zero_write, + const vr_values *rvals) { int idx = -1; strinfo *si = NULL; @@ -5076,16 +5355,23 @@ is_char_type (tree type) } /* Check the built-in call at GSI for validity and optimize it. + Uses RVALS to determine range information. Return true to let the caller advance *GSI to the next statement in the basic block and false otherwise. */ static bool -strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, - bool *zero_write, +strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, bool *zero_write, const vr_values *rvals) { gimple *stmt = gsi_stmt (*gsi); + if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) + { + tree fntype = gimple_call_fntype (stmt); + if (fntype && lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (fntype))) + handle_alloc_call (BUILT_IN_NONE, gsi); + } + /* When not optimizing we must be checking printf calls which we do even for user-defined functions when they are declared with attribute format. */ @@ -5108,7 +5394,7 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, case BUILT_IN_STRCPY_CHK: case BUILT_IN_STPCPY: case BUILT_IN_STPCPY_CHK: - handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi); + handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi, rvals); break; case BUILT_IN_STRNCAT: @@ -5127,18 +5413,20 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, case BUILT_IN_MEMCPY_CHK: case BUILT_IN_MEMPCPY: case BUILT_IN_MEMPCPY_CHK: - handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi); + handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi, rvals); break; case BUILT_IN_STRCAT: case BUILT_IN_STRCAT_CHK: handle_builtin_strcat (DECL_FUNCTION_CODE (callee), gsi); break; + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: case BUILT_IN_MALLOC: case BUILT_IN_CALLOC: - handle_builtin_malloc (DECL_FUNCTION_CODE (callee), gsi); + handle_alloc_call (DECL_FUNCTION_CODE (callee), gsi); break; case BUILT_IN_MEMSET: - if (handle_builtin_memset (gsi, zero_write)) + if (handle_builtin_memset (gsi, zero_write, rvals)) return false; break; case BUILT_IN_MEMCMP: @@ -5163,7 +5451,8 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, If GSI's basic block needs clean-up of EH, set *CLEANUP_EH to true. */ static void -handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh) +handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh, + const vr_values *rvals) { gimple *stmt = gsi_stmt (*gsi); tree lhs = gimple_assign_lhs (stmt); @@ -5266,6 +5555,31 @@ handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh) } } } + else if (code == MEM_REF && TREE_CODE (lhs) == SSA_NAME) + { + if (int idx = new_stridx (lhs)) + { + /* Record multi-byte assignments from MEM_REFs. */ + bool storing_all_nonzero_p; + bool storing_all_zeros_p; + bool full_string_p; + unsigned lenrange[] = { UINT_MAX, 0, 0 }; + tree rhs = gimple_assign_rhs1 (stmt); + const bool ranges_valid + = count_nonzero_bytes (rhs, lenrange, &full_string_p, + &storing_all_zeros_p, &storing_all_nonzero_p, + rvals); + if (ranges_valid) + { + tree length = build_int_cst (sizetype, lenrange[0]); + strinfo *si = new_strinfo (lhs, idx, length, full_string_p); + set_strinfo (idx, si); + si->writable = true; + si->dont_invalidate = true; + maybe_warn_overflow (stmt, lenrange[2], rvals); + } + } + } if (strlen_to_stridx) { @@ -5318,29 +5632,35 @@ check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh, } else if (TREE_CODE (lhs) == SSA_NAME && INTEGRAL_TYPE_P (lhs_type)) /* Handle assignment to a character. */ - handle_integral_assign (gsi, cleanup_eh); + handle_integral_assign (gsi, cleanup_eh, rvals); else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs)) { tree type = TREE_TYPE (lhs); if (TREE_CODE (type) == ARRAY_TYPE) type = TREE_TYPE (type); - bool is_char_store = is_char_type (type); - if (!is_char_store && TREE_CODE (lhs) == MEM_REF) - { - /* To consider stores into char objects via integer types - other than char but not those to non-character objects, - determine the type of the destination rather than just - the type of the access. */ - tree ref = TREE_OPERAND (lhs, 0); - type = TREE_TYPE (ref); - if (TREE_CODE (type) == POINTER_TYPE) - type = TREE_TYPE (type); - if (TREE_CODE (type) == ARRAY_TYPE) - type = TREE_TYPE (type); - if (is_char_type (type)) - is_char_store = true; - } + bool is_char_store = is_char_type (type); + if (!is_char_store && TREE_CODE (lhs) == MEM_REF) + { + /* To consider stores into char objects via integer types + other than char but not those to non-character objects, + determine the type of the destination rather than just + the type of the access. */ + for (int i = 0; i != 2; ++i) + { + tree ref = TREE_OPERAND (lhs, i); + type = TREE_TYPE (ref); + if (TREE_CODE (type) == POINTER_TYPE) + type = TREE_TYPE (type); + if (TREE_CODE (type) == ARRAY_TYPE) + type = TREE_TYPE (type); + if (is_char_type (type)) + { + is_char_store = true; + break; + } + } + } /* Handle a single or multibyte assignment. */ if (is_char_store && !handle_store (gsi, &zero_write, rvals)) diff --git a/gcc/tree-ssa-strlen.h b/gcc/tree-ssa-strlen.h index 4d43fc65e9e..46f2c0a3996 100644 --- a/gcc/tree-ssa-strlen.h +++ b/gcc/tree-ssa-strlen.h @@ -25,8 +25,10 @@ extern bool is_strlen_related_p (tree, tree); extern bool maybe_diag_stxncpy_trunc (gimple_stmt_iterator, tree, tree); extern tree set_strlen_range (tree, wide_int, wide_int, tree = NULL_TREE); -struct c_strlen_data; class vr_values; +extern tree get_range (tree, wide_int[2], const vr_values * = NULL); + +struct c_strlen_data; extern void get_range_strlen_dynamic (tree , c_strlen_data *, const vr_values *); /* APIs internal to strlen pass. Defined in in gimple-ssa-sprintf.c. */ diff --git a/gcc/tree.c b/gcc/tree.c index d4d9d62cfc6..f7033b43f9f 100644 --- a/gcc/tree.c +++ b/gcc/tree.c @@ -13583,8 +13583,8 @@ get_initializer_for (tree init, tree decl) determine the size of an initialized flexible array member. If non-null, *INTERIOR_ZERO_LENGTH is set when REF refers to an interior zero-length array. - Returns the size (which might be zero for an object with - an uninitialized flexible array member) or null if the size + Returns the size as sizetype (which might be zero for an object + with an uninitialized flexible array member) or null if the size cannot be determined. */ tree @@ -13733,7 +13733,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */) memsz64 -= baseoff; return wide_int_to_tree (TREE_TYPE (memsize), memsz64); } - return integer_zero_node; + return size_zero_node; } /* Return "don't know" for an external non-array object since its @@ -13744,7 +13744,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */) && DECL_EXTERNAL (base) && (!typematch || TREE_CODE (basetype) != ARRAY_TYPE) - ? NULL_TREE : integer_zero_node); + ? NULL_TREE : size_zero_node); } /* Return the machine mode of T. For vectors, returns the mode of the -- 2.30.2