From 83685efd5fd1623cfc4e4c435ce2773d95d458d1 Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Fri, 9 Oct 2020 14:48:43 -0600 Subject: [PATCH] Generalize compute_objsize to return maximum size/offset instead of failing (PR middle-end/97023). Also resolves: PR middle-end/97342 - bogus -Wstringop-overflow with nonzero signed and unsigned offsets PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy PR middle-end/96384 - bogus -Wstringop-overflow= storing into multidimensional array with index in range gcc/ChangeLog: PR middle-end/97342 PR middle-end/97023 PR middle-end/96384 * builtins.c (access_ref::access_ref): Initialize new member. Use new enum. (access_ref::size_remaining): Define new member function. (inform_access): Handle expressions referencing objects. (gimple_call_alloc_size): Call get_size_range instead of get_range. (gimple_call_return_array): New function. (get_range): Rename... (get_offset_range): ...to this. Improve detection of ranges from types of expressions. (gimple_call_return_array): Adjust calls to get_range per above. (compute_objsize): Same. Set maximum size or offset instead of failing for unknown objects and handle more kinds of expressions. (compute_objsize): Call access_ref::size_remaining. (compute_objsize): Have transitional wrapper fail for pointers into unknown objects. (expand_builtin_strncmp): Call access_ref::size_remaining and handle new cases. * builtins.h (access_ref::size_remaining): Declare new member function. (access_ref::set_max_size_range): Define new member function. (access_ref::add_ofset, access_ref::add_max_ofset): Same. (access_ref::add_base0): New data member. * calls.c (get_size_range): Change argument type. Handle new condition. * calls.h (get_size_range): Adjust signature. (enum size_range_flags): Define new type. * gimple-ssa-warn-restrict.c (builtin_memref::builtin_memref): Correct argument to get_size_range. * tree-ssa-strlen.c (get_range): Handle anti-ranges. (maybe_warn_overflow): Check DECL_P before assuming it's one. gcc/testsuite/ChangeLog: PR middle-end/97342 PR middle-end/97023 PR middle-end/96384 * c-c++-common/Wrestrict.c: Adjust comment. * gcc.dg/Wstringop-overflow-34.c: Remove xfail. * gcc.dg/Wstringop-overflow-43.c: Remove xfails. Adjust regex patterns. * gcc.dg/pr51683.c: Prune out expected warning. * gcc.target/i386/pr60693.c: Same. * g++.dg/warn/Wplacement-new-size-8.C: New test. * gcc.dg/Wstringop-overflow-41.c: New test. * gcc.dg/Wstringop-overflow-44.s: New test. * gcc.dg/Wstringop-overflow-45.c: New test. * gcc.dg/Wstringop-overflow-46.c: New test. * gcc.dg/Wstringop-overflow-47.c: New test. * gcc.dg/Wstringop-overflow-49.c: New test. * gcc.dg/Wstringop-overflow-50.c: New test. * gcc.dg/Wstringop-overflow-51.c: New test. * gcc.dg/Wstringop-overflow-52.c: New test. * gcc.dg/Wstringop-overflow-53.c: New test. * gcc.dg/Wstringop-overflow-54.c: New test. * gcc.dg/Wstringop-overflow-55.c: New test. * gcc.dg/Wstringop-overread-5.c: New test. --- gcc/builtins.c | 659 +++++++++++++----- gcc/builtins.h | 41 +- gcc/calls.c | 69 +- gcc/calls.h | 10 +- gcc/gimple-ssa-warn-restrict.c | 2 +- gcc/testsuite/c-c++-common/Wrestrict.c | 13 +- .../g++.dg/warn/Wplacement-new-size-8.C | 147 ++++ gcc/testsuite/gcc.dg/Wstringop-overflow-34.c | 2 +- gcc/testsuite/gcc.dg/Wstringop-overflow-41.c | 120 ++++ gcc/testsuite/gcc.dg/Wstringop-overflow-43.c | 9 +- gcc/testsuite/gcc.dg/Wstringop-overflow-44.s | 271 +++++++ gcc/testsuite/gcc.dg/Wstringop-overflow-45.c | 255 +++++++ gcc/testsuite/gcc.dg/Wstringop-overflow-46.c | 97 +++ gcc/testsuite/gcc.dg/Wstringop-overflow-47.c | 69 ++ gcc/testsuite/gcc.dg/Wstringop-overflow-49.c | 146 ++++ gcc/testsuite/gcc.dg/Wstringop-overflow-50.c | 125 ++++ gcc/testsuite/gcc.dg/Wstringop-overflow-51.c | 34 + gcc/testsuite/gcc.dg/Wstringop-overflow-52.c | 62 ++ gcc/testsuite/gcc.dg/Wstringop-overflow-53.c | 116 +++ gcc/testsuite/gcc.dg/Wstringop-overflow-54.c | 103 +++ gcc/testsuite/gcc.dg/Wstringop-overflow-55.c | 97 +++ gcc/testsuite/gcc.dg/Wstringop-overread-5.c | 76 ++ gcc/testsuite/gcc.dg/pr51683.c | 3 + gcc/testsuite/gcc.target/i386/pr60693.c | 3 + gcc/tree-ssa-strlen.c | 14 +- 25 files changed, 2339 insertions(+), 204 deletions(-) create mode 100644 gcc/testsuite/g++.dg/warn/Wplacement-new-size-8.C create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-41.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-44.s create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-45.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-46.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-47.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-49.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-50.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-51.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-52.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-53.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-54.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-55.c create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overread-5.c diff --git a/gcc/builtins.c b/gcc/builtins.c index 283c1e6674c..3a77da2b10c 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -183,8 +183,6 @@ static void maybe_emit_chk_warning (tree, enum built_in_function); static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function); static void maybe_emit_free_warning (tree); static tree fold_builtin_object_size (tree, tree); -static bool get_range (tree, gimple *, signop, offset_int[2], - range_query * = NULL); static bool check_read_access (tree, tree, tree = NULL_TREE, int = 1); unsigned HOST_WIDE_INT target_newline; @@ -200,7 +198,7 @@ static void expand_builtin_sync_synchronize (void); access_ref::access_ref (tree bound /* = NULL_TREE */, bool minaccess /* = false */) -: ref (), eval ([](tree x){ return x; }), trail1special (true) +: ref (), eval ([](tree x){ return x; }), trail1special (true), base0 (true) { /* Set to valid. */ offrng[0] = offrng[1] = 0; @@ -215,7 +213,7 @@ access_ref::access_ref (tree bound /* = NULL_TREE */, set the bounds of the access to reflect both it and MINACCESS. BNDRNG[0] is the size of the minimum access. */ tree rng[2]; - if (bound && get_size_range (bound, rng, true)) + if (bound && get_size_range (bound, rng, SR_ALLOW_ZERO)) { bndrng[0] = wi::to_offset (rng[0]); bndrng[1] = wi::to_offset (rng[1]); @@ -223,6 +221,131 @@ access_ref::access_ref (tree bound /* = NULL_TREE */, } } +/* Return the maximum amount of space remaining and if non-null, set + argument to the minimum. */ + +offset_int +access_ref::size_remaining (offset_int *pmin /* = NULL */) const +{ + offset_int minbuf; + if (!pmin) + pmin = &minbuf; + + /* add_offset() ensures the offset range isn't inverted. */ + gcc_checking_assert (offrng[0] <= offrng[1]); + + if (base0) + { + /* The offset into referenced object is zero-based (i.e., it's + not referenced by a pointer into middle of some unknown object). */ + if (offrng[0] < 0 && offrng[1] < 0) + { + /* If the offset is negative the remaining size is zero. */ + *pmin = 0; + return 0; + } + + if (sizrng[1] <= offrng[0]) + { + /* If the starting offset is greater than or equal to the upper + bound on the size of the object, the space remaining is zero. + As a special case, if it's equal, set *PMIN to -1 to let + the caller know the offset is valid and just past the end. */ + *pmin = sizrng[1] == offrng[0] ? -1 : 0; + return 0; + } + + /* Otherwise return the size minus the lower bound of the offset. */ + offset_int or0 = offrng[0] < 0 ? 0 : offrng[0]; + + *pmin = sizrng[0] - or0; + return sizrng[1] - or0; + } + + /* The offset to the referenced object isn't zero-based (i.e., it may + refer to a byte other than the first. The size of such an object + is constrained only by the size of the address space (the result + of max_object_size()). */ + if (sizrng[1] <= offrng[0]) + { + *pmin = 0; + return 0; + } + + offset_int or0 = offrng[0] < 0 ? 0 : offrng[0]; + + *pmin = sizrng[0] - or0; + return sizrng[1] - or0; +} + +/* Add the range [MIN, MAX] to the offset range. For known objects (with + zero-based offsets) at least one of whose offset's bounds is in range, + constrain the other (or both) to the bounds of the object (i.e., zero + and the upper bound of its size). This improves the quality of + diagnostics. */ + +void access_ref::add_offset (const offset_int &min, const offset_int &max) +{ + if (min <= max) + { + /* To add an ordinary range just add it to the bounds. */ + offrng[0] += min; + offrng[1] += max; + } + else if (!base0) + { + /* To add an inverted range to an offset to an unknown object + expand it to the maximum. */ + add_max_offset (); + return; + } + else + { + /* To add an inverted range to an offset to an known object set + the upper bound to the maximum representable offset value + (which may be greater than MAX_OBJECT_SIZE). + The lower bound is either the sum of the current offset and + MIN when abs(MAX) is greater than the former, or zero otherwise. + Zero because then then inverted range includes the negative of + the lower bound. */ + offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + offrng[1] = maxoff; + + if (max >= 0) + { + offrng[0] = 0; + return; + } + + offrng[1] = maxoff; + offset_int absmax = wi::abs (max); + if (offrng[0] < absmax) + offrng[0] += min; + else + offrng[0] = 0; + } + + if (!base0) + return; + + /* When referencing a known object check to see if the offset computed + so far is in bounds... */ + offset_int remrng[2]; + remrng[1] = size_remaining (remrng); + if (remrng[1] > 0 || remrng[0] < 0) + { + /* ...if so, constrain it so that neither bound exceeds the size of + the object. Out of bounds offsets are left unchanged, and, for + better or worse, become in bounds later. They should be detected + and diagnosed at the point they first become invalid by + -Warray-bounds. */ + if (offrng[0] < 0) + offrng[0] = 0; + if (offrng[1] > sizrng[1]) + offrng[1] = sizrng[1]; + } +} + /* Return true if NAME starts with __builtin_ or __sync_. */ static bool @@ -3757,15 +3880,24 @@ inform_access (const access_ref &ref, access_mode mode) if (!ref.ref) return; - /* Convert offset range and avoid including a zero range since it isn't - necessarily meaningful. */ - long long minoff = 0, maxoff = 0; - if (wi::fits_shwi_p (ref.offrng[0]) - && wi::fits_shwi_p (ref.offrng[1])) - { - minoff = ref.offrng[0].to_shwi (); - maxoff = ref.offrng[1].to_shwi (); - } + /* Convert offset range and avoid including a zero range since it + isn't necessarily meaningful. */ + HOST_WIDE_INT diff_min = tree_to_shwi (TYPE_MIN_VALUE (ptrdiff_type_node)); + HOST_WIDE_INT diff_max = tree_to_shwi (TYPE_MAX_VALUE (ptrdiff_type_node)); + HOST_WIDE_INT minoff; + HOST_WIDE_INT maxoff = diff_max; + if (wi::fits_shwi_p (ref.offrng[0])) + minoff = ref.offrng[0].to_shwi (); + else + minoff = ref.offrng[0] < 0 ? diff_min : diff_max; + + if (wi::fits_shwi_p (ref.offrng[1])) + maxoff = ref.offrng[1].to_shwi (); + + if (maxoff <= diff_min || maxoff >= diff_max) + /* Avoid mentioning an upper bound that's equal to or in excess + of the maximum of ptrdiff_t. */ + maxoff = minoff; /* Convert size range and always include it since all sizes are meaningful. */ @@ -3799,23 +3931,27 @@ inform_access (const access_ref &ref, access_mode mode) sprintf (sizestr, "[%llu, %llu]", minsize, maxsize); } - else + else if (DECL_P (ref.ref)) loc = DECL_SOURCE_LOCATION (ref.ref); + else if (EXPR_P (ref.ref) && EXPR_HAS_LOCATION (ref.ref)) + loc = EXPR_LOCATION (ref.ref); + else + return; if (mode == access_read_write || mode == access_write_only) { - if (DECL_P (ref.ref)) + if (allocfn == NULL_TREE) { if (minoff == maxoff) { if (minoff == 0) - inform (loc, "destination object %qD", ref.ref); + inform (loc, "destination object %qE", ref.ref); else - inform (loc, "at offset %lli into destination object %qD", + inform (loc, "at offset %wi into destination object %qE", minoff, ref.ref); } else - inform (loc, "at offset [%lli, %lli] into destination object %qD", + inform (loc, "at offset [%wi, %wi] into destination object %qE", minoff, maxoff, ref.ref); return; } @@ -3827,12 +3963,12 @@ inform_access (const access_ref &ref, access_mode mode) sizestr, allocfn); else inform (loc, - "at offset %lli into destination object of size %s " + "at offset %wi into destination object of size %s " "allocated by %qE", minoff, sizestr, allocfn); } else inform (loc, - "at offset [%lli, %lli] into destination object of size %s " + "at offset [%wi, %wi] into destination object of size %s " "allocated by %qE", minoff, maxoff, sizestr, allocfn); @@ -3846,11 +3982,11 @@ inform_access (const access_ref &ref, access_mode mode) if (minoff == 0) inform (loc, "source object %qD", ref.ref); else - inform (loc, "at offset %lli into source object %qD", + inform (loc, "at offset %wi into source object %qD", minoff, ref.ref); } else - inform (loc, "at offset [%lli, %lli] into source object %qD", + inform (loc, "at offset [%wi, %wi] into source object %qD", minoff, maxoff, ref.ref); return; } @@ -3862,12 +3998,12 @@ inform_access (const access_ref &ref, access_mode mode) sizestr, allocfn); else inform (loc, - "at offset %lli into source object of size %s " + "at offset %wi into source object of size %s " "allocated by %qE", minoff, sizestr, allocfn); } else inform (loc, - "at offset [%lli, %lli] into source object of size %s " + "at offset [%wi, %wi] into source object of size %s " "allocated by %qE", minoff, maxoff, sizestr, allocfn); } @@ -4152,8 +4288,14 @@ check_access (tree exp, tree dstwrite, && range[0] && TREE_CODE (slen) == INTEGER_CST && tree_int_cst_lt (slen, range[0])); - - if (!overread && pad && pad->src.sizrng[1] >= 0 && pad->src.offrng[0] >= 0) + /* If none is determined try to get a better answer based on the details + in PAD. */ + if (!overread + && pad + && pad->src.sizrng[1] >= 0 + && pad->src.offrng[0] >= 0 + && (pad->src.offrng[1] < 0 + || pad->src.offrng[0] <= pad->src.offrng[1])) { /* Set RANGE to that of MAXREAD, bounded by PAD->SRC.BNDRNG if PAD is nonnull and BNDRNG is valid. */ @@ -4215,7 +4357,7 @@ check_read_access (tree exp, tree src, tree bound /* = NULL_TREE */, tree gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, - range_query *rvals /* = NULL */) + range_query * /* = NULL */) { if (!stmt) return NULL_TREE; @@ -4267,14 +4409,17 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, if (!rng1) rng1 = rng1_buf; + /* Use maximum precision to avoid overflow below. */ const int prec = ADDR_MAX_PRECISION; - const tree size_max = TYPE_MAX_VALUE (sizetype); - if (!get_range (size, stmt, rng1, rvals)) - { - /* Use the full non-negative range on failure. */ - rng1[0] = wi::zero (prec); - rng1[1] = wi::to_wide (size_max, prec); - } + + { + tree r[2]; + /* Determine the largest valid range size, including zero. */ + if (!get_size_range (size, r, SR_ALLOW_ZERO | SR_USE_LARGEST)) + return NULL_TREE; + rng1[0] = wi::to_wide (r[0], prec); + rng1[1] = wi::to_wide (r[1], prec); + } if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST) return fold_convert (sizetype, size); @@ -4283,26 +4428,24 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, 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 (!get_range (n, stmt, rng2, rvals)) - { + { + tree r[2]; /* As above, use the full non-negative range on failure. */ - rng2[0] = wi::zero (prec); - rng2[1] = wi::to_wide (size_max, prec); - } - - /* Extend to the maximum precision to avoid overflow. */ - 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); + if (!get_size_range (n, r, SR_ALLOW_ZERO | SR_USE_LARGEST)) + return NULL_TREE; + rng2[0] = wi::to_wide (r[0], prec); + rng2[1] = wi::to_wide (r[1], prec); + } /* 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]; + + const tree size_max = TYPE_MAX_VALUE (sizetype); if (wi::gtu_p (rng1[1], wi::to_wide (size_max, prec))) { - rng1[1] = wi::to_wide (size_max); + rng1[1] = wi::to_wide (size_max, prec); return size_max; } @@ -4315,7 +4458,8 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, Return the function parameter on success and null otherwise. */ tree -gimple_parm_array_size (tree ptr, wide_int rng[2], range_query * /* = NULL */) +gimple_parm_array_size (tree ptr, wide_int rng[2], + range_query * /* = NULL */) { /* For a function argument try to determine the byte size of the array from the current function declaratation (e.g., attribute access or @@ -4363,27 +4507,43 @@ gimple_parm_array_size (tree ptr, wide_int rng[2], range_query * /* = NULL */) return var; } -/* Wrapper around the wide_int overload of get_range. Returns the same - result but accepts offset_int instead. */ +/* Wrapper around the wide_int overload of get_range that accepts + offset_int instead. For middle end expressions returns the same + result. For a subset of nonconstamt expressions emitted by the front + end determines a more precise range than would be possible otherwise. */ static bool -get_range (tree x, gimple *stmt, signop sgn, offset_int r[2], - range_query *rvals /* = NULL */) +get_offset_range (tree x, gimple *stmt, offset_int r[2], range_query *rvals) { - tree type = TREE_TYPE (x); - if (TREE_CODE (x) != INTEGER_CST - && TREE_CODE (x) != SSA_NAME) + offset_int add = 0; + if (TREE_CODE (x) == PLUS_EXPR) { - if (TYPE_UNSIGNED (type)) + /* Handle constant offsets in pointer addition expressions seen + n the front end IL. */ + tree op = TREE_OPERAND (x, 1); + if (TREE_CODE (op) == INTEGER_CST) { - if (sgn == SIGNED) - type = signed_type_for (type); + op = fold_convert (signed_type_for (TREE_TYPE (op)), op); + add = wi::to_offset (op); + x = TREE_OPERAND (x, 0); } - else if (sgn == UNSIGNED) - type = unsigned_type_for (type); + } + + if (TREE_CODE (x) == NOP_EXPR) + /* Also handle conversions to sizetype seen in the front end IL. */ + x = TREE_OPERAND (x, 0); + + tree type = TREE_TYPE (x); - r[0] = wi::to_offset (TYPE_MIN_VALUE (type)); - r[1] = wi::to_offset (TYPE_MAX_VALUE (type)); + if (TREE_CODE (x) != INTEGER_CST + && TREE_CODE (x) != SSA_NAME) + { + if (TYPE_UNSIGNED (type) + && TYPE_PRECISION (type) == TYPE_PRECISION (sizetype)) + type = signed_type_for (type); + + r[0] = wi::to_offset (TYPE_MIN_VALUE (type)) + add; + r[1] = wi::to_offset (TYPE_MAX_VALUE (type)) + add; return x; } @@ -4391,6 +4551,7 @@ get_range (tree x, gimple *stmt, signop sgn, offset_int r[2], if (!get_range (x, stmt, wr, rvals)) return false; + signop sgn = SIGNED; /* Only convert signed integers or unsigned sizetype to a signed offset and avoid converting large positive values in narrower types to negative offsets. */ @@ -4403,6 +4564,83 @@ get_range (tree x, gimple *stmt, signop sgn, offset_int r[2], return true; } +/* Return the argument that the call STMT to a built-in function returns + or null if it doesn't. On success, set OFFRNG[] to the range of offsets + from the argument reflected in the value returned by the built-in if it + can be determined, otherwise to 0 and HWI_M1U respectively. */ + +static tree +gimple_call_return_array (gimple *stmt, offset_int offrng[2], + range_query *rvals) +{ + if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL) + || gimple_call_num_args (stmt) < 1) + return NULL_TREE; + + tree fn = gimple_call_fndecl (stmt); + switch (DECL_FUNCTION_CODE (fn)) + { + case BUILT_IN_MEMCPY: + case BUILT_IN_MEMCPY_CHK: + case BUILT_IN_MEMMOVE: + case BUILT_IN_MEMMOVE_CHK: + case BUILT_IN_MEMSET: + case BUILT_IN_STPCPY: + case BUILT_IN_STPCPY_CHK: + case BUILT_IN_STPNCPY: + case BUILT_IN_STPNCPY_CHK: + case BUILT_IN_STRCAT: + case BUILT_IN_STRCAT_CHK: + case BUILT_IN_STRCPY: + case BUILT_IN_STRCPY_CHK: + case BUILT_IN_STRNCAT: + case BUILT_IN_STRNCAT_CHK: + case BUILT_IN_STRNCPY: + case BUILT_IN_STRNCPY_CHK: + offrng[0] = offrng[1] = 0; + return gimple_call_arg (stmt, 0); + + case BUILT_IN_MEMPCPY: + case BUILT_IN_MEMPCPY_CHK: + { + tree off = gimple_call_arg (stmt, 2); + if (!get_offset_range (off, stmt, offrng, rvals)) + { + offrng[0] = 0; + offrng[1] = HOST_WIDE_INT_M1U; + } + return gimple_call_arg (stmt, 0); + } + + case BUILT_IN_MEMCHR: + { + tree off = gimple_call_arg (stmt, 2); + if (get_offset_range (off, stmt, offrng, rvals)) + offrng[0] = 0; + else + { + offrng[0] = 0; + offrng[1] = HOST_WIDE_INT_M1U; + } + return gimple_call_arg (stmt, 0); + } + + case BUILT_IN_STRCHR: + case BUILT_IN_STRRCHR: + case BUILT_IN_STRSTR: + { + offrng[0] = 0; + offrng[1] = HOST_WIDE_INT_M1U; + } + return gimple_call_arg (stmt, 0); + + default: + break; + } + + return NULL_TREE; +} + /* Helper to compute the size of the object referenced by the PTR expression which must have pointer type, using Object Size type OSTYPE (only the least significant 2 bits are used). @@ -4412,7 +4650,8 @@ get_range (tree x, gimple *stmt, signop sgn, offset_int r[2], the object(s). VISITED is used to avoid visiting the same PHI operand multiple times, and, when nonnull, RVALS to determine range information. - Returns true on success, false when the size cannot be determined. + Returns true on success, false when a meaningful size (or range) + cannot be determined. The function is intended for diagnostics and should not be used to influence code generation or optimization. */ @@ -4429,25 +4668,40 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, if (DECL_P (ptr)) { - /* Bail if the reference is to the pointer itself (as opposed - to what it points to). */ + pref->ref = ptr; + if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr))) - return false; + { + /* Set the maximum size if the reference is to the pointer + itself (as opposed to what it points to). */ + pref->set_max_size_range (); + return true; + } - pref->ref = ptr; if (tree size = decl_init_size (ptr, false)) if (TREE_CODE (size) == INTEGER_CST) { pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size); return true; } - pref->sizrng[0] = 0; - pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + + pref->set_max_size_range (); return true; } const tree_code code = TREE_CODE (ptr); + if (code == BIT_FIELD_REF) + { + tree ref = TREE_OPERAND (ptr, 0); + if (!compute_objsize (ref, ostype, pref, visited, rvals)) + return false; + + offset_int off = wi::to_offset (pref->eval (TREE_OPERAND (ptr, 2))); + pref->add_offset (off / BITS_PER_UNIT); + return true; + } + if (code == COMPONENT_REF) { tree ref = TREE_OPERAND (ptr, 0); @@ -4455,27 +4709,29 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, if (ostype == 0) { - /* For raw memory functions like memcpy bail if the size - of the enclosing object cannot be determined. */ - if (!compute_objsize (ref, ostype, pref, visited, rvals) - || !pref->ref) + /* In OSTYPE zero (for raw memory functions like memcpy), use + the maximum size instead if the identity of the enclosing + object cannot be determined. */ + if (!compute_objsize (ref, ostype, pref, visited, rvals)) return false; /* Otherwise, use the size of the enclosing object and add the offset of the member to the offset computed so far. */ tree offset = byte_position (field); - if (TREE_CODE (offset) != INTEGER_CST) - return false; - offset_int off = wi::to_offset (offset); - pref->offrng[0] += off; - pref->offrng[1] += off; + if (TREE_CODE (offset) == INTEGER_CST) + pref->add_offset (wi::to_offset (offset)); + else + pref->add_max_offset (); return true; } - /* Bail if the reference is to the pointer itself (as opposed - to what it points to). */ if (!addr && POINTER_TYPE_P (TREE_TYPE (field))) - return false; + { + /* Set maximum size if the reference is to the pointer member + itself (as opposed to what it points to). */ + pref->set_max_size_range (); + return true; + } pref->ref = field; @@ -4530,9 +4786,12 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, offset_int orng[2]; tree off = pref->eval (TREE_OPERAND (ptr, 1)); - if (!get_range (off, NULL, SIGNED, orng, rvals)) - /* Fail unless the size of the object is zero. */ - return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1]; + if (!get_offset_range (off, NULL, orng, rvals)) + { + /* Set ORNG to the maximum offset representable in ptrdiff_t. */ + orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + orng[0] = -orng[1] - 1; + } if (TREE_CODE (ptr) == ARRAY_REF) { @@ -4551,7 +4810,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, tree eltype = TREE_TYPE (ptr); tree tpsize = TYPE_SIZE_UNIT (eltype); if (!tpsize || TREE_CODE (tpsize) != INTEGER_CST) - return false; + { + pref->add_max_offset (); + return true; + } offset_int sz = wi::to_offset (tpsize); orng[0] *= sz; @@ -4578,12 +4840,44 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, } } - pref->offrng[0] += orng[0]; - pref->offrng[1] += orng[1]; + pref->add_offset (orng[0], orng[1]); + return true; + } + if (code == TARGET_MEM_REF) + { + tree ref = TREE_OPERAND (ptr, 0); + if (!compute_objsize (ref, ostype, pref, visited, rvals)) + return false; + + /* TODO: Handle remaining operands. Until then, add maximum offset. */ + pref->ref = ptr; + pref->add_max_offset (); + return true; + } + + if (code == INTEGER_CST) + { + /* Pointer constants other than null are most likely the result + of erroneous null pointer addition/subtraction. Set size to + zero. For null pointers, set size to the maximum for now + since those may be the result of jump threading. */ + if (integer_zerop (ptr)) + pref->set_max_size_range (); + else + pref->sizrng[0] = pref->sizrng[1] = 0; + pref->ref = ptr; + + return true; + } + + if (code == STRING_CST) + { + pref->sizrng[0] = pref->sizrng[1] = TREE_STRING_LENGTH (ptr); return true; } - else if (code == POINTER_PLUS_EXPR) + + if (code == POINTER_PLUS_EXPR) { tree ref = TREE_OPERAND (ptr, 0); if (!compute_objsize (ref, ostype, pref, visited, rvals)) @@ -4591,16 +4885,14 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, offset_int orng[2]; tree off = pref->eval (TREE_OPERAND (ptr, 1)); - if (!get_range (off, NULL, SIGNED, orng, rvals)) - /* Fail unless the size of the object is zero. */ - return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1]; - - pref->offrng[0] += orng[0]; - pref->offrng[1] += orng[1]; - + if (get_offset_range (off, NULL, orng, rvals)) + pref->add_offset (orng[0], orng[1]); + else + pref->add_max_offset (); return true; } - else if (code == VIEW_CONVERT_EXPR) + + if (code == VIEW_CONVERT_EXPR) { ptr = TREE_OPERAND (ptr, 0); return compute_objsize (ptr, ostype, pref, visited, rvals); @@ -4612,17 +4904,53 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, if (is_gimple_call (stmt)) { /* If STMT is a call to an allocation function get the size - from its argument(s). If successful, also set *PDECL to - PTR for the caller to include in diagnostics. */ + from its argument(s). If successful, also set *PREF->REF + to PTR for the caller to include in diagnostics. */ wide_int wr[2]; if (gimple_call_alloc_size (stmt, wr, rvals)) { pref->ref = ptr; pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); - return true; + /* Constrain both bounds to a valid size. */ + offset_int maxsize = wi::to_offset (max_object_size ()); + if (pref->sizrng[0] > maxsize) + pref->sizrng[0] = maxsize; + if (pref->sizrng[1] > maxsize) + pref->sizrng[1] = maxsize; } - return false; + else + { + /* For functions known to return one of their pointer arguments + try to determine what the returned pointer points to, and on + success add OFFRNG which was set to the offset added by + the function (e.g., memchr) to the overall offset. */ + offset_int offrng[2]; + if (tree ret = gimple_call_return_array (stmt, offrng, rvals)) + { + if (!compute_objsize (ret, ostype, pref, visited, rvals)) + return false; + + /* Cap OFFRNG[1] to at most the remaining size of + the object. */ + offset_int remrng[2]; + remrng[1] = pref->size_remaining (remrng); + if (remrng[1] < offrng[1]) + offrng[1] = remrng[1]; + pref->add_offset (offrng[0], offrng[1]); + } + else + { + /* For other calls that might return arbitrary pointers + including into the middle of objects set the size + range to maximum, clear PREF->BASE0, and also set + PREF->REF to include in diagnostics. */ + pref->set_max_size_range (); + pref->base0 = false; + pref->ref = ptr; + } + } + return true; } if (gimple_nop_p (stmt)) @@ -4631,62 +4959,74 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, of the array from the current function declaratation (e.g., attribute access or related). */ wide_int wr[2]; - tree ref = gimple_parm_array_size (ptr, wr, rvals); - if (!ref) - return NULL_TREE; - pref->ref = ref; - pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); - pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); + if (tree ref = gimple_parm_array_size (ptr, wr, rvals)) + { + pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); + pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); + pref->ref = ref; + return true; + } + + pref->set_max_size_range (); + pref->base0 = false; + pref->ref = ptr; + if (tree var = SSA_NAME_VAR (ptr)) + if (TREE_CODE (var) == PARM_DECL) + pref->ref = var; + return true; } /* TODO: Handle PHI. */ if (!is_gimple_assign (stmt)) - return false; + { + /* Clear BASE0 since the assigned pointer might point into + the middle of the object, set the maximum size range and, + if the SSA_NAME refers to a function argumnent, set + PREF->REF to it. */ + pref->base0 = false; + pref->set_max_size_range (); + if (tree var = SSA_NAME_VAR (ptr)) + if (TREE_CODE (var) == PARM_DECL) + pref->ref = var; + return true; + } ptr = gimple_assign_rhs1 (stmt); tree_code code = gimple_assign_rhs_code (stmt); - if (TREE_CODE (TREE_TYPE (ptr)) != POINTER_TYPE) - /* Avoid conversions from non-pointers. */ - return false; - if (code == POINTER_PLUS_EXPR) + if (code == POINTER_PLUS_EXPR + && TREE_CODE (TREE_TYPE (ptr)) == POINTER_TYPE) { - /* If the the offset in the expression can be determined use - it to adjust the overall offset. Otherwise, set the overall - offset to the maximum. */ + /* Compute the size of the object first. */ + if (!compute_objsize (ptr, ostype, pref, visited, rvals)) + return false; + offset_int orng[2]; tree off = gimple_assign_rhs2 (stmt); - if (!get_range (off, stmt, SIGNED, orng, rvals)) - { - orng[0] = wi::to_offset (TYPE_MIN_VALUE (ptrdiff_type_node)); - orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); - } - - pref->offrng[0] += orng[0]; - pref->offrng[1] += orng[1]; + if (get_offset_range (off, stmt, orng, rvals)) + pref->add_offset (orng[0], orng[1]); + else + pref->add_max_offset (); + return true; } - else if (code != ADDR_EXPR) - return false; - return compute_objsize (ptr, ostype, pref, visited, rvals); - } - - tree type = TREE_TYPE (ptr); - type = TYPE_MAIN_VARIANT (type); - if (TREE_CODE (ptr) == ADDR_EXPR) - ptr = TREE_OPERAND (ptr, 0); + if (code == ADDR_EXPR) + return compute_objsize (ptr, ostype, pref, visited, rvals); - if (TREE_CODE (type) == ARRAY_TYPE - && !array_at_struct_end_p (ptr)) - { - if (tree size = TYPE_SIZE_UNIT (type)) - return get_range (size, NULL, UNSIGNED, pref->sizrng, rvals); + /* This could be an assignment from a nonlocal pointer. Save PTR + to mention in diagnostics but otherwise treat it as a pointer + to an unknown object. */ + pref->ref = ptr; } - return false; + /* Assume all other expressions point into an unknown object + of the maximum valid size. */ + pref->base0 = false; + pref->set_max_size_range (); + return true; } /* A "public" wrapper around the above. Clients should use this overload @@ -4707,27 +5047,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, if (!success) return NULL_TREE; - if (pref->offrng[1] < pref->offrng[0]) - { - if (pref->offrng[1] < 0 - && pref->sizrng[1] <= pref->offrng[0]) - return size_zero_node; - - return wide_int_to_tree (sizetype, pref->sizrng[1]); - } - - if (pref->offrng[0] < 0) - { - if (pref->offrng[1] < 0) - return size_zero_node; - - pref->offrng[0] = 0; - } - - if (pref->sizrng[1] <= pref->offrng[0]) - return size_zero_node; - - return wide_int_to_tree (sizetype, pref->sizrng[1] - pref->offrng[0]); + offset_int maxsize = pref->size_remaining (); + if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0) + pref->offrng[0] = 0; + return wide_int_to_tree (sizetype, maxsize); } /* Transitional wrapper around the above. The function should be removed @@ -4735,13 +5058,13 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, tree compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */, - tree *poff /* = NULL */, class range_query *rvals /* = NULL */) + tree *poff /* = NULL */, range_query *rvals /* = NULL */) { /* Set the initial offsets to zero and size to negative to indicate none has been computed yet. */ access_ref ref; tree size = compute_objsize (ptr, ostype, &ref, rvals); - if (!size) + if (!size || !ref.base0) return NULL_TREE; if (pdecl) @@ -6020,13 +6343,21 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target, tree size2 = compute_objsize (arg2, 1, &ref2); tree func = get_callee_fndecl (exp); - if (size1 && size2) + if (size1 && size2 && bndrng[0] && !integer_zerop (bndrng[0])) { - tree maxsize = tree_int_cst_le (size1, size2) ? size2 : size1; - - if (tree_int_cst_lt (maxsize, bndrng[0])) + offset_int rem1 = ref1.size_remaining (); + offset_int rem2 = ref2.size_remaining (); + if (rem1 == 0 || rem2 == 0) maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func, - bndrng, maxsize); + bndrng, integer_zero_node); + else + { + offset_int maxrem = wi::max (rem1, rem2, UNSIGNED); + if (maxrem < wi::to_offset (bndrng[0])) + maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, + func, bndrng, + wide_int_to_tree (sizetype, maxrem)); + } } else if (bndrng[0] && !integer_zerop (bndrng[0]) diff --git a/gcc/builtins.h b/gcc/builtins.h index f226c63e198..c09f36da02b 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -180,15 +180,45 @@ struct access_ref return offrng[0] == 0 && offrng[1] == 0; } - /* Return true if OFFRNG is bounded to a subrange of possible offset - values. */ + /* Return true if OFFRNG is bounded to a subrange of offset values + valid for the largest possible object. */ bool offset_bounded () const; + /* Return the maximum amount of space remaining and if non-null, set + argument to the minimum. */ + offset_int size_remaining (offset_int * = NULL) const; + + /* Set the size range to the maximum. */ + void set_max_size_range () + { + sizrng[0] = 0; + sizrng[1] = wi::to_offset (max_object_size ()); + } + + /* Add OFF to the offset range. */ + void add_offset (const offset_int &off) + { + add_offset (off, off); + } + + /* Add the range [MIN, MAX] to the offset range. */ + void add_offset (const offset_int &, const offset_int &); + + /* Add the maximum representable offset to the offset range. */ + void add_max_offset () + { + offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + add_offset (-maxoff - 1, maxoff); + } + /* Used to fold integer expressions when called from front ends. */ tree (*eval)(tree); /* Set if trailing one-element arrays should be treated as flexible array members. */ bool trail1special; + /* Set if valid offsets must start at zero (for declared and allocated + objects but not for others referenced by pointers). */ + bool base0; }; /* Describes a pair of references used in an access by built-in @@ -216,11 +246,10 @@ class range_query; extern tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL, range_query * = NULL); extern tree gimple_parm_array_size (tree, wide_int[2], range_query * = NULL); +extern tree compute_objsize (tree, int, access_ref *, range_query * = NULL); extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL, range_query * = NULL); -extern tree compute_objsize (tree, int, access_ref *, range_query * = NULL); - -extern bool check_access (tree, tree, tree, tree, tree, access_mode, - const access_data * = NULL); +extern bool check_access (tree, tree, tree, tree, tree, + access_mode, const access_data * = NULL); #endif /* GCC_BUILTINS_H */ diff --git a/gcc/calls.c b/gcc/calls.c index 93da3d6256e..d3120b23f60 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -1235,14 +1235,16 @@ alloc_max_size (void) after adjusting it if necessary to make EXP a represents a valid size of object, or a valid size argument to an allocation function declared with attribute alloc_size (whose argument may be signed), or to a string - manipulation function like memset. When ALLOW_ZERO is true, allow - returning a range of [0, 0] for a size in an anti-range [1, N] where - N > PTRDIFF_MAX. A zero range is a (nearly) invalid argument to - allocation functions like malloc but it is a valid argument to - functions like memset. */ + manipulation function like memset. + When ALLOW_ZERO is set in FLAGS, allow returning a range of [0, 0] for + a size in an anti-range [1, N] where N > PTRDIFF_MAX. A zero range is + a (nearly) invalid argument to allocation functions like malloc but it + is a valid argument to functions like memset. + When USE_LARGEST is set in FLAGS set RANGE to the largest valid subrange + in a multi-range, otherwise to the smallest valid subrange. */ bool -get_size_range (tree exp, tree range[2], bool allow_zero /* = false */) +get_size_range (tree exp, tree range[2], int flags /* = 0 */) { if (!exp) return false; @@ -1314,25 +1316,50 @@ get_size_range (tree exp, tree range[2], bool allow_zero /* = false */) min = wi::zero (expprec); } } - else if (wi::eq_p (0, min - 1)) + else { - /* EXP is unsigned and not in the range [1, MAX]. That means - it's either zero or greater than MAX. Even though 0 would - normally be detected by -Walloc-zero, unless ALLOW_ZERO - is true, set the range to [MAX, TYPE_MAX] so that when MAX - is greater than the limit the whole range is diagnosed. */ - if (allow_zero) - min = max = wi::zero (expprec); - else + wide_int maxsize = wi::to_wide (max_object_size ()); + min = wide_int::from (min, maxsize.get_precision (), UNSIGNED); + max = wide_int::from (max, maxsize.get_precision (), UNSIGNED); + if (wi::eq_p (0, min - 1)) { + /* EXP is unsigned and not in the range [1, MAX]. That means + it's either zero or greater than MAX. Even though 0 would + normally be detected by -Walloc-zero, unless ALLOW_ZERO + is set, set the range to [MAX, TYPE_MAX] so that when MAX + is greater than the limit the whole range is diagnosed. */ + wide_int maxsize = wi::to_wide (max_object_size ()); + if (flags & SR_ALLOW_ZERO) + { + if (wi::leu_p (maxsize, max + 1) + || !(flags & SR_USE_LARGEST)) + min = max = wi::zero (expprec); + else + { + min = max + 1; + max = wi::to_wide (TYPE_MAX_VALUE (exptype)); + } + } + else + { + min = max + 1; + max = wi::to_wide (TYPE_MAX_VALUE (exptype)); + } + } + else if ((flags & SR_USE_LARGEST) + && wi::ltu_p (max + 1, maxsize)) + { + /* When USE_LARGEST is set and the larger of the two subranges + is a valid size, use it... */ min = max + 1; - max = wi::to_wide (TYPE_MAX_VALUE (exptype)); + max = maxsize; + } + else + { + /* ...otherwise use the smaller subrange. */ + max = min - 1; + min = wi::zero (expprec); } - } - else - { - max = min - 1; - min = wi::zero (expprec); } } diff --git a/gcc/calls.h b/gcc/calls.h index dfb951ca45b..644ec45d92c 100644 --- a/gcc/calls.h +++ b/gcc/calls.h @@ -133,7 +133,15 @@ extern bool reference_callee_copied (CUMULATIVE_ARGS *, extern void maybe_warn_alloc_args_overflow (tree, tree, tree[2], int[2]); extern tree get_attr_nonstring_decl (tree, tree * = NULL); extern bool maybe_warn_nonstring_arg (tree, tree); -extern bool get_size_range (tree, tree[2], bool = false); +enum size_range_flags + { + /* Set to consider zero a valid range. */ + SR_ALLOW_ZERO = 1, + /* Set to use the largest subrange of a set of ranges as opposed + to the smallest. */ + SR_USE_LARGEST = 2 + }; +extern bool get_size_range (tree, tree[2], int = 0); extern rtx rtx_for_static_chain (const_tree, bool); extern bool cxx17_empty_base_field_p (const_tree); diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c index 512fc138528..e2734c81456 100644 --- a/gcc/gimple-ssa-warn-restrict.c +++ b/gcc/gimple-ssa-warn-restrict.c @@ -264,7 +264,7 @@ builtin_memref::builtin_memref (tree expr, tree size) tree range[2]; /* Determine the size range, allowing for the result to be [0, 0] for SIZE in the anti-range ~[0, N] where N >= PTRDIFF_MAX. */ - get_size_range (size, range, true); + get_size_range (size, range, SR_ALLOW_ZERO); sizrange[0] = wi::to_offset (range[0]); sizrange[1] = wi::to_offset (range[1]); /* get_size_range returns SIZE_MAX for the maximum size. diff --git a/gcc/testsuite/c-c++-common/Wrestrict.c b/gcc/testsuite/c-c++-common/Wrestrict.c index 3b019c8a80e..9eb02bdbfcb 100644 --- a/gcc/testsuite/c-c++-common/Wrestrict.c +++ b/gcc/testsuite/c-c++-common/Wrestrict.c @@ -320,14 +320,17 @@ void test_memcpy_anti_range (char *d, const char *s) T (d, d + SAR (0, 3), UR (DIFF_MAX - 2, DIFF_MAX)); /* { dg-warning "accessing \[0-9\]+ or more bytes at offsets 0 and \\\[-?\[0-9\]+, -?\[0-9\]+] overlaps \[0-9\]+ bytes at offset 2" "memcpy" } */ - /* Verify that a size in an anti-range ~[0, N] where N >= PTRDIFF_MAX - doesn't trigger a warning. */ + /* Verify that a size in an anti-range ~[1, N] where N >= PTRDIFF_MAX - 2 + doesn't trigger a warning. + With ~[1, PTRDIFF_MAX - 1] the difference between the just-past-the-end + pointer to A and A for char A[PTRDIFF_MAX] wouldn't be representable in + ptrdiff_t. Since such a large object cannot exist, so the size of + the region must be zero. */ + T (d, s, UAR (1, DIFF_MAX / 2 - 1)); T (d, s, UAR (1, DIFF_MAX - 1)); T (d, s, UAR (1, DIFF_MAX)); T (d, s, UAR (1, SIZE_MAX - 1)); - - /* This causes the last dg-warning test to fail for some reason. - T (d, s, UAR (1, SIZE_MAX)); */ + T (d, s, UAR (1, SIZE_MAX)); } /* Verify calls to memcpy() where the combination of offsets in some diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-8.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-8.C new file mode 100644 index 00000000000..a0d136266ea --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-8.C @@ -0,0 +1,147 @@ +/* Verify informational notes following the warning. + { dg-do compile } + { dg-options "-Wall" } */ + +#define DISS_MAX __PTRDIFF_MAX__ +#define SIZE_MAX __SIZE_MAX__ + +typedef __SIZE_TYPE__ size_t; + +template struct S { char a[N]; }; + +void* operator new (size_t, void *p) { return p; } +void* operator new[] (size_t, void *p) { return p; } + + +void test_cst_off () +{ + { + char ca0[0]; // { dg-message "'ca0' declared here" "note" } + new (ca0 + 0) S<1>; // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[0]' and size '0'" } + } + { + char ca1[1]; + new (ca1 + 0) S<1>; + } + { + char ca1[1]; // { dg-message "'ca1' declared here" "note" } + new (ca1 + 0) S<2>; // { dg-warning "constructing an object of type 'S<2>' and size '2' in a region of type 'char \\\[1]' and size '1'" } + } + { + char ca1[1]; // { dg-message "at offset 1 from 'ca1' declared here" "note" } + new (ca1 + 1) S<1>; // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[1]' and size '0'" } + } + { + char ca1[1]; // { dg-message "at offset 2 from 'ca1' declared here" "note" } + new (ca1 + 2) S<1>; // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[1]' and size '0'" } + } + { + char ca1[1]; // { dg-message "at offset -1 from 'ca1' declared here" "note" } + new (ca1 - 1) S<1>; // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[1]' and size '0'" } + } + { + /* Offsets are treated as signed so SIZE_MAX is indistinguishable + from -1. */ + char ca1[1]; // { dg-message "at offset \\d+ from 'ca1' declared here" "note" { xfail *-*-* } } + // { dg-message "at offset -1 from 'ca1' declared here" "note" { target *-*-* } .-1 } + new (ca1 + SIZE_MAX) S<1>; // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[1]' and size '0'" } + } +} + + +/* Verify that the range of the offset included in the note corresponds + to the range of its type (plus the optional constant). */ + +void test_var_off_uchar (unsigned char i) +{ + { + // Verify that the nore doesn't mention an offset. + char ca0[0]; // { dg-message ": 'ca0' declared here" "note" } + new (ca0 + i) S<1>; // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[0]' and size '0'" } + } + { + char ca1[1]; + new (ca1 + i) S<1>; + } + { + // Verify that the nore doesn't mention an offset. + char ca1[1]; // { dg-message ": 'ca1' declared here" "note" } + new (ca1 + i) S<2>; // { dg-warning "constructing an object of type 'S<2>' and size '2' in a region of type 'char \\\[1]' and size at most '1'" } + } + { + char ca2[2]; + new (ca2 + i) S<2>; + new (ca2 + 1 - i) S<2>; + new (ca2 - i + 1) S<2>; + new (ca2 - 2 + i) S<2>; + new (ca2 - i + 2) S<2>; + new (ca2 - i + i) S<2>; + new (ca2 + i + i) S<2>; + } + { + char ca2[2]; // { dg-message "at offset \\\[1, 2] from 'ca2' declared here" "note" } + new (ca2 + i + 1) S<2>; // { dg-warning "constructing an object of type 'S<2>' and size '2' in a region of type 'char \\\[2]' and size at most '1'" } + } + + { + char a[65281]; + new (a + i + 65280) S<1>; + } + { + char a[65281]; // { dg-message "at offset 65281 from 'a' declared here" "note" } + new (a + i + 65281) S<1>; // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[65281]' and size '0'" } + } + { + char a[65281]; // { dg-message "at offset \\\[65154, 65281] from 'a' declared here" "note" } + new (a + i + 65154) S<128>; // { dg-warning "constructing an object of type 'S<128>' and size '128' in a region of type 'char \\\[65281]' and size at most '127'" } + } +} + + +/* Same as above but also verify that the signedness of the offset is + considered in the issuing the warning. */ + +void test_var_off_schar (signed char i) +{ + { + // Verify that the nore doesn't mention an offset. + char ca0[0]; // { dg-message ": 'ca0' declared here" "note" } + new (ca0 + i) S<1>; // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[0]' and size '0'" } + } + { + char ca1[1]; + new (ca1 + i) S<1>; + new (ca1 - i) S<1>; + new (ca1 + i + 1) S<1>; + new (ca1 - i + 1) S<1>; + new (ca1 + i + i) S<1>; + new (ca1 - i - i) S<1>; + } + { + // Verify that the nore doesn't mention an offset. + char ca1[1]; // { dg-message ": 'ca1' declared here" "note" } + new (ca1 + i) S<2>; // { dg-warning "constructing an object of type 'S<2>' and size '2' in a region of type 'char \\\[1]' and size at most '1'" } + } + { + char ca2[2]; + new (ca2 + i) S<2>; + new (ca2 + 1 - i) S<2>; + new (ca2 - i + 1) S<2>; + new (ca2 - 2 + i) S<2>; + new (ca2 - i + 2) S<2>; + new (ca2 - i + i) S<2>; + new (ca2 + i + i) S<2>; + } + { + char ca2[2]; + new (ca2 + i + 1) S<2>; + } + + { + char a[65281]; // { dg-message "at offset \\\[65153, 65408] from 'a'" } + new (a + i + 65280) S<1>; + new (a + i + 65281) S<1>; + new (a + i + 65281) S<128>; + new (a + i + 65281) S<129>; // { dg-warning "constructing an object of type 'S<129>' and size '129' in a region of type 'char \\\[65281]' and size at most '128'" } + } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-34.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-34.c index fd43f3afb59..a1b103918cf 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-34.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-34.c @@ -112,7 +112,7 @@ void s2_warn_cstoff_cstidx (struct S2 *p) void s2_warn_varoff_cstdix (struct S2 *p, int i) { char *q = p->a + i; - q[2] = __LINE__; // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } } + q[2] = __LINE__; // { dg-warning "\\\[-Wstringop-overflow" } } void s2_warn_cstoff_varidx (struct S2 *p, int i) diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c new file mode 100644 index 00000000000..9b2d2cbc501 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c @@ -0,0 +1,120 @@ +/* Verify that writes at excessive offsets into declared or allocated + objects of unknown size are diagnosed. + { dg-do compile } + { dg-options "-O2" } */ + +#define DIFF_MAX __PTRDIFF_MAX__ + +typedef __SIZE_TYPE__ size_t; + +void* malloc (size_t); +void* memcpy (void*, const void*, size_t); +void* memset (void*, int, size_t); + +void sink (void*); + + +void char_array_cst_off_cst_size (void) +{ + extern char caxcc[]; // { dg-message "at offset \\d+ into destination object 'caxcc'" } + + char *p = caxcc; + size_t idx = DIFF_MAX - 3; + + memset (p + idx, 0, 3); + sink (p); + + ++idx; + memset (p + idx, 0, 3); // { dg-warning "writing 3 bytes into a region of size 2" } + sink (p); + + ++idx; + memset (p + idx, 0, 3); // { dg-warning "writing 3 bytes into a region of size 1" "pr?????" { xfail ilp32 } } + + ++idx; + memset (p + idx, 0, 3); // { dg-warning "writing 3 bytes into a region of size 0" } + sink (p); +} + + +void char_array_var_off_cst_size (size_t idx) +{ + /* The offset is a range with a very large lower bound and an upper + bound of DIFF_MAX. There's not point in also mentioning the latter + (it wouldn't make the note any more meaningful) so verify it only + mentions the lower bound. */ + extern char caxvc[]; // { dg-message "at offset \\d+ into destination object 'caxvc'" "note" } + + char *p = caxvc; + + if (idx < DIFF_MAX - 3) + idx = DIFF_MAX - 3; + + memset (p + idx, 0, 3); + sink (p); + + memset (p + idx, 0, 5); // { dg-warning "writing 5 bytes into a region of size 3" } + sink (p); +} + + +void char_array_var_off_var_size (size_t idx, size_t n) +{ + extern char caxvv[]; // { dg-message "at offset \\d+ into destination object 'caxvv'" "note" } + + char *p = caxvv; + + if (idx < DIFF_MAX - 3) + idx = DIFF_MAX - 3; + + if (n < 3 || 7 < n) + n = 3; + + memset (p + idx, 0, n); + sink (p); + + ++n; + memset (p + idx, 0, n); // { dg-warning "writing between 4 and 8 bytes into a region of size 3" } + sink (p); +} + + +void alloc_array_var_off_cst_size (size_t n, size_t idx) +{ + char *p = malloc (n); // { dg-message "at offset \\d+ into destination object" "note" } + + if (idx < DIFF_MAX - 3) + idx = DIFF_MAX - 3; + + memset (p + idx, 0, 3); + sink (p); + + memset (p + idx, 0, 5); // { dg-warning "writing 5 bytes into a region of size 3" } + sink (p); +} + + +void int_array_cst_off_cst_size (void) +{ + extern int iaxc[]; // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'iaxc'" } + + int *p = iaxc; + size_t idx = DIFF_MAX / sizeof *iaxc; + + memset (p + idx, 0, 3); + sink (p); + + memset (p + idx, 0, 5); // { dg-warning "writing 5 bytes into a region of size 3" } + sink (p); +} + + +void* nowarn_anti_range_1 (char *p, char *q) +{ + size_t n = q - p; + if (!n) return 0; + + char *d = __builtin_malloc (n + 1); + memcpy (d, p, n + 1); // { dg-bogus "-Wstringop-overflow" } + return d; +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-43.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-43.c index 3ac5a88e4b0..14ab925afdc 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-43.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-43.c @@ -159,9 +159,10 @@ void warn_memset_reversed_range (void) char *p = &a11[11]; - /* The below is represented as a true anti-range as opposed to a range - with reversed bounds and the former aren't handled. */ - T1 (p, SAR (INT_MIN, -11), n11); // { dg-warning "writing 11 or more bytes into a region of size 0" "pr?????" { xfail *-*-* } } + /* Since the offset is excessive, either starting before &a11[0] + ot just past &a[11], the region size in the warning should + probably be zero, but accept other sizes too. */ + T1 (p, SAR (INT_MIN, -11), n11); // { dg-warning "writing 11 or more bytes into a region of size \\d+" } /* The following are represented as ordinary ranges with reversed bounds and those are handled. */ @@ -171,7 +172,7 @@ void warn_memset_reversed_range (void) T1 (p, SAR (INT_MIN, 1), n11); // { dg-warning "writing 11 or more bytes into a region of size 0" "pr?????" { xfail ilp32 } } T1 (p, SAR (INT_MIN, 0), n11); // { dg-warning "writing 11 or more bytes into a region of size 0" } /* Also represented as a true anti-range. */ - T1 (p, SAR ( -12, -11), n11); // { dg-warning "writing 11 or more bytes into a region of size 0" "pr?????" { xfail *-*-* } } + T1 (p, SAR ( -12, -11), n11); // { dg-warning "writing 11 or more bytes into a region of size \\d+" } T1 (p, SAR ( -12, -1), n11); // { dg-warning "writing 11 or more bytes into a region of size 0" } T1 (p, SAR ( -11, 0), n11); // { dg-warning "writing 11 or more bytes into a region of size 0" } T1 (p, SAR ( -11, 11), n11); // { dg-warning "writing 11 or more bytes into a region of size 0" } diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-44.s b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.s new file mode 100644 index 00000000000..0fc73a99078 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.s @@ -0,0 +1,271 @@ + .file "Wstringop-overflow-44.c" + .text + .p2align 4 + .globl f0 + .type f0, @function +f0: +.LFB0: + .cfi_startproc + ret + .cfi_endproc +.LFE0: + .size f0, .-f0 + .p2align 4 + .globl f1 + .type f1, @function +f1: +.LFB1: + .cfi_startproc + ret + .cfi_endproc +.LFE1: + .size f1, .-f1 + .p2align 4 + .globl f2 + .type f2, @function +f2: +.LFB2: + .cfi_startproc + movl n(%rip), %eax + testl %eax, %eax + jle .L12 +.L4: + ret + .p2align 4,,10 + .p2align 3 +.L12: + movslq %eax, %rdx + movq d(%rip), %rcx + testq %rdx, %rdx + je .L4 + xorl %eax, %eax +.L6: + movb $0, (%rcx,%rax) + addq $1, %rax + cmpq %rdx, %rax + jb .L6 + ret + .cfi_endproc +.LFE2: + .size f2, .-f2 + .p2align 4 + .globl f3 + .type f3, @function +f3: +.LFB3: + .cfi_startproc + movslq n(%rip), %rdx + testl %edx, %edx + jle .L15 + ret + .p2align 4,,10 + .p2align 3 +.L15: + movq %rdi, %rsi + movq d(%rip), %rdi + jmp strncpy + .cfi_endproc +.LFE3: + .size f3, .-f3 + .p2align 4 + .globl f4 + .type f4, @function +f4: +.LFB4: + .cfi_startproc + movl n(%rip), %eax + testl %eax, %eax + jle .L18 + ret + .p2align 4,,10 + .p2align 3 +.L18: + movq d(%rip), %rax + movq %rdi, %rsi + movb $0, (%rax) + movslq n(%rip), %rdx + movq d(%rip), %rdi + jmp strncat + .cfi_endproc +.LFE4: + .size f4, .-f4 + .p2align 4 + .globl g0 + .type g0, @function +g0: +.LFB5: + .cfi_startproc + movl n(%rip), %eax + testl %eax, %eax + jle .L25 + ret + .p2align 4,,10 + .p2align 3 +.L25: + subq $24, %rsp + .cfi_def_cfa_offset 32 + leaq 15(%rsp), %rdi + call sink + addq $24, %rsp + .cfi_def_cfa_offset 8 + ret + .cfi_endproc +.LFE5: + .size g0, .-g0 + .p2align 4 + .globl g1 + .type g1, @function +g1: +.LFB6: + .cfi_startproc + movl n(%rip), %eax + testl %eax, %eax + jle .L32 + ret + .p2align 4,,10 + .p2align 3 +.L32: + subq $24, %rsp + .cfi_def_cfa_offset 32 + leaq 15(%rsp), %rdi + call sink + addq $24, %rsp + .cfi_def_cfa_offset 8 + ret + .cfi_endproc +.LFE6: + .size g1, .-g1 + .p2align 4 + .globl g2 + .type g2, @function +g2: +.LFB7: + .cfi_startproc + movl n(%rip), %eax + testl %eax, %eax + jle .L45 + ret + .p2align 4,,10 + .p2align 3 +.L45: + movslq %eax, %rdx + subq $24, %rsp + .cfi_def_cfa_offset 32 + testq %rdx, %rdx + je .L36 + xorl %eax, %eax +.L35: + movb $0, 15(%rsp,%rax) + addq $1, %rax + cmpq %rdx, %rax + jb .L35 +.L36: + leaq 15(%rsp), %rdi + call sink + addq $24, %rsp + .cfi_def_cfa_offset 8 + ret + .cfi_endproc +.LFE7: + .size g2, .-g2 + .p2align 4 + .globl g3 + .type g3, @function +g3: +.LFB8: + .cfi_startproc + movslq n(%rip), %rdx + testl %edx, %edx + jle .L52 + ret + .p2align 4,,10 + .p2align 3 +.L52: + subq $24, %rsp + .cfi_def_cfa_offset 32 + movq %rdi, %rsi + leaq 15(%rsp), %rdi + call strncpy + leaq 15(%rsp), %rdi + call sink + addq $24, %rsp + .cfi_def_cfa_offset 8 + ret + .cfi_endproc +.LFE8: + .size g3, .-g3 + .p2align 4 + .globl g4 + .type g4, @function +g4: +.LFB9: + .cfi_startproc + movslq n(%rip), %rdx + testl %edx, %edx + jle .L59 + ret + .p2align 4,,10 + .p2align 3 +.L59: + subq $24, %rsp + .cfi_def_cfa_offset 32 + movq %rdi, %rsi + leaq 15(%rsp), %rdi + movb $0, 15(%rsp) + call strncat + leaq 15(%rsp), %rdi + call sink + addq $24, %rsp + .cfi_def_cfa_offset 8 + ret + .cfi_endproc +.LFE9: + .size g4, .-g4 + .p2align 4 + .globl h0 + .type h0, @function +h0: +.LFB10: + .cfi_startproc + movl n(%rip), %eax + testl %eax, %eax + jle .L66 + ret + .p2align 4,,10 + .p2align 3 +.L66: + subq $8, %rsp + .cfi_def_cfa_offset 16 + movl $1, %edi + call malloc + movq %rax, d(%rip) + addq $8, %rsp + .cfi_def_cfa_offset 8 + ret + .cfi_endproc +.LFE10: + .size h0, .-h0 + .p2align 4 + .globl h1 + .type h1, @function +h1: +.LFB16: + .cfi_startproc + movl n(%rip), %eax + testl %eax, %eax + jle .L73 + ret + .p2align 4,,10 + .p2align 3 +.L73: + subq $8, %rsp + .cfi_def_cfa_offset 16 + movl $1, %edi + call malloc + movq %rax, d(%rip) + addq $8, %rsp + .cfi_def_cfa_offset 8 + ret + .cfi_endproc +.LFE16: + .size h1, .-h1 diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c new file mode 100644 index 00000000000..112d79a5ae7 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c @@ -0,0 +1,255 @@ +/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy + Verify that out of bounds writes by built-ins to objects through pointers + returned by other built-ins are diagnosed. + { dg-do compile } + { dg-options "-O2" } */ + +#include "range.h" + +void* malloc (size_t); +void* memcpy (void*, const void*, size_t); +void* memmove (void*, const void*, size_t); +void* mempcpy (void*, const void*, size_t); + +void sink (void*, ...); + + +void nowarn_memcpy (const void *s) +{ + extern char cpy_a4[4]; + unsigned n = sizeof cpy_a4; + + void *p = cpy_a4; + p = memcpy (p, s, n); + sink (p); + memcpy (p, s, n); + sink (p); + + p = cpy_a4 + 1; + p = memcpy (p, s, n - 1); + sink (p); + memcpy (p, s, n - 1); + sink (p); + + p = cpy_a4 + 2; + p = memcpy (p, s, n - 2); + sink (p); + memcpy (p, s, n - 2); + sink (p); + + p = cpy_a4 + 3; + p = memcpy (p, s, n - 3); + sink (p); + memcpy (p, s, n - 3); + sink (p); + + p = cpy_a4 + 4; + p = memcpy (p, s, n - 4); + sink (p); + memcpy (p, s, n - 4); + sink (p); +} + + +void nowarn_memcpy_chain (const void *s) +{ + extern char cpy_a8[8]; + + char *p = cpy_a8; + + p = memcpy (p + 1, s, 7); + sink (p); + + p = memcpy (p + 2 , s, 5); + sink (p); + + p = memcpy (p + 3 , s, 2); + sink (p); + + p = memcpy (p + 1 , s, 1); + sink (p); + + p = memcpy (p - 7 , s, 8); + sink (p); + + memcpy (p + 1, s, 7); +} + + +void warn_memcpy (const void *s) +{ + extern char cpy_a5[5]; // { dg-message "destination object 'cpy_a5'" "note" } + + unsigned n = sizeof cpy_a5; + void *p = cpy_a5; + + p = memcpy (p, s, n); + sink (p); + memcpy (p, s, n + 1); // { dg-warning "writing 6 bytes into a region of size 5" } + sink (p); + + p = cpy_a5; + p = memcpy (p, s, n); + sink (p); + memcpy (p, s, n + 1); // { dg-warning "writing 6 bytes into a region of size 5" } + sink (p); + + p = cpy_a5 + 1; + p = memcpy (p, s, n - 1); + sink (p); + memcpy (p, s, n); // { dg-warning "writing 5 bytes into a region of size 4" } + sink (p); +} + + +void warn_memcpy_chain (const void *s) +{ + extern char cpy_a8[8]; // { dg-message "destination object 'cpy_a8'" "note" } + + char *p = cpy_a8; + + p = memcpy (p, s, 9); // { dg-warning "writing 9 bytes into a region of size 8" } + sink (p); + + p = memcpy (p + 2, s, 7); // { dg-warning "writing 7 bytes into a region of size 6" } + sink (p); + + p = memcpy (p + 3, s, 5); // { dg-warning "writing 5 bytes into a region of size 3" } + sink (p); + + p = memcpy (p + 3, s, 3); // { dg-warning "writing 3 bytes into a region of size 0" } + sink (p); +} + + +void nowarn_mempcpy (const void *s) +{ + extern char a4[4]; + unsigned n = sizeof a4; + + char *p = mempcpy (a4, s, n); + sink (p); + mempcpy (p - 4, s, n); + sink (p); + + p = mempcpy (a4 + 1, s, n - 1); + sink (p); + mempcpy (p - 4, s, n); + sink (p); + + p = mempcpy (a4 + 2, s, n - 2); + sink (p); + mempcpy (p - 4, s, n); + sink (p); + + p = mempcpy (a4 + 3, s, n - 3); + sink (p); + mempcpy (p - 4, s, n); + sink (p); + + p = mempcpy (a4 + 4, s, n - 4); + sink (p); + mempcpy (p - 4, s, n); + sink (p); +} + + +void nowarn_mempcpy_chain (const void *s) +{ + extern char pcpy_a8[8]; + + char *p = pcpy_a8; + + p = mempcpy (p + 1, s, 7); + sink (p); + + p = mempcpy (p - 7 , s, 7); + sink (p); + + p = mempcpy (p - 5 , s, 5); + sink (p); + + p = mempcpy (p - 3 , s, 3); + sink (p); + + p = mempcpy (p - 2 , s, 2); + sink (p); + + mempcpy (p - 1, s, 1); + sink (p); + + mempcpy (p - 8, s, 8); +} + + +void warn_mempcpy (const void *s) +{ + extern char pcpy_a5[5]; // { dg-message "destination object 'pcpy_a5'" "note" } + + char *p = pcpy_a5; + + p = mempcpy (p, s, 5); + sink (p); + mempcpy (p - 5, s, 6); // { dg-warning "writing 6 bytes into a region of size 5 " } + sink (p); + + p = pcpy_a5; + p = mempcpy (p, s, 3); + sink (p); + mempcpy (p, s, 3); // { dg-warning "writing 3 bytes into a region of size 2 " } + sink (p); + + p = pcpy_a5 + 1; + p = mempcpy (p, s, 3); + sink (p); + mempcpy (p - 1, s, 5); // { dg-warning "writing 5 bytes into a region of size 2 " } + sink (p); +} + + +void warn_mempcpy_chain_3 (const void *s) +{ + char *p = malloc (5); // { dg-message "at offset \\\[3, 5] into destination object of size 5" "note" } + p = mempcpy (p, s, UR (1, 2)); + p = mempcpy (p, s, UR (2, 3)); + p = mempcpy (p, s, UR (3, 4)); // { dg-warning "writing between 3 and 4 bytes into a region of size 2 " } + + sink (p); +} + +void warn_mempcpy_offrng_chain_3 (const void *s) +{ + char *p = malloc (11); // { dg-message "at offset \\\[9, 11] into destination object of size 11 " "note" } + size_t r1_2 = UR (1, 2); + size_t r2_3 = r1_2 + 1; + size_t r3_4 = r2_3 + 1; + + p = mempcpy (p + r1_2, s, r1_2); + p = mempcpy (p + r2_3, s, r2_3); + p = mempcpy (p + r3_4, s, r3_4); // { dg-warning "writing between 3 and 4 bytes into a region of size 2 " } + + sink (p); +} + +void warn_mempcpy_chain_4 (const void *s) +{ + char *p = malloc (9); // { dg-message "at offset \\\[6, 9] into destination object of size 9 " "note" } + p = mempcpy (p, s, UR (1, 2)); + p = mempcpy (p, s, UR (2, 3)); + p = mempcpy (p, s, UR (3, 4)); + p = mempcpy (p, s, UR (4, 5)); // { dg-warning "writing between 4 and 5 bytes into a region of size 3 " } + + sink (p); +} + +void warn_mempcpy_chain_5 (const void *s) +{ + char *p = malloc (14); // { dg-message "at offset \\\[10, 14] into destination object of size 14 " "note" } + p = mempcpy (p, s, UR (1, 2)); + p = mempcpy (p, s, UR (2, 3)); + p = mempcpy (p, s, UR (3, 4)); + p = mempcpy (p, s, UR (4, 5)); + p = mempcpy (p, s, UR (5, 6)); // { dg-warning "writing between 5 and 6 bytes into a region of size 4 " } + + sink (p); +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c new file mode 100644 index 00000000000..a4d78b21cd1 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c @@ -0,0 +1,97 @@ +/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy + Verify that out of bounds writes by built-ins to objects through pointers + returned by memchr() are diagnosed. + { dg-do compile } + { dg-options "-O2" } */ + +#include "range.h" + +void* malloc (size_t); +void* memchr (void*, int, size_t); +void* memset (void*, int, size_t); + +void sink (void*, ...); + +void nowarn_memchr_cst_memset_cst (const void *s) +{ + char *p = malloc (4); + sink (p); + + p = memchr (p, '1', 4); + memset (p, 0, 4); +} + +void nowarn_memchr_uint_memset_cst (const void *s, unsigned n) +{ + char *p = malloc (4); + sink (p); + + p = memchr (p, '1', n); + memset (p, 0, 4); +} + +void nowarn_memchr_sz_memset_cst (const void *s, size_t n) +{ + char *p = malloc (4); + sink (p); + + p = memchr (p, '1', n); + memset (p, 0, 4); +} + +void nowarn_memchr_anti_range_memset_cst (const void *s, size_t n) +{ + char *p = malloc (4); + sink (p); + + if (n == 0) + n = 1; + + p = memchr (p, '1', n); + memset (p, 0, 4); +} + +void warn_memchr_cst_memset_cst (const void *s) +{ + char *p = malloc (4); // { dg-message "at offset \\\[0, 4] into destination object of size 4 " "note" } + sink (p); + + p = memchr (p, '1', 4); + memset (p, 0, 5); // { dg-warning "writing 5 bytes into a region of size 4 " } +} + +void warn_memchr_var_memset_cst (const void *s, unsigned n) +{ + char *p = malloc (4); // { dg-message "at offset \\\[0, 4] into destination object of size 4 " "note" } + sink (p); + + p = memchr (p, '1', n); + memset (p, 0, 5); // { dg-warning "writing 5 bytes into a region of size 4 " } +} + +void warn_memchr_var_memset_range (const void *s, unsigned n) +{ + /* The offsets in the first two notes are bounded by the size of + the allocated object. The real upper bound of the offset in + the last note includes the upper bound f the offset of the pointer + returned from the previous memchr() call, but it ends up getting + constrained to the bounds of the allocated object so it's the same + as in the first two notes. The exact value probably isn't too + important. */ + char *p0 = malloc (UR (5, 7)); + // { dg-message "at offset \\\[0, 7] into destination object of size \\\[5, 7]" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[1, 7] into destination object of size \\\[5, 7]" "note" { target *-*-* } .-2 } + // { dg-message "at offset \\\[2, 7] into destination object of size \\\[5, 7]" "note" { target *-*-* } .-3 } + + sink (p0); + char *p1 = memchr (p0, '1', n); + memset (p1, 0, UR (8, 9)); // { dg-warning "writing between 8 and 9 bytes into a region of size 7 " } + + sink (p0); + p1 = memchr (p0 + 1, '2', n); + memset (p1, 0, UR (7, 9)); // { dg-warning "writing between 7 and 9 bytes into a region of size 6 " } + + sink (p0); + char *p2 = memchr (p1 + 1, '3', n); + memset (p2, 0, UR (6, 9)); // { dg-warning "writing between 6 and 9 bytes into a region of size 5 " } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c new file mode 100644 index 00000000000..02b14ee2eda --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c @@ -0,0 +1,69 @@ +/* Verify that storing a bigger vector into smaller space is diagnosed. + { dg-do compile } + { dg-options "-O2" } */ + +typedef __INT16_TYPE__ int16_t; +typedef __attribute__ ((__vector_size__ (32))) char C32; + +typedef __attribute__ ((__vector_size__ (64))) int16_t I16_64; + +void sink (void*); + + +void nowarn_c32 (char c) +{ + extern char nowarn_a32[32]; + + void *p = nowarn_a32; + *(C32*)p = (C32){ c }; + sink (p); + + char a32[32]; + p = a32; + *(C32*)p = (C32){ c }; + sink (p); +} + +void warn_c32 (char c) +{ + extern char warn_a32[32]; // { dg-message "at offset 32 to object 'warn_a32' with size 32" } + + void *p = warn_a32 + 1; + *(C32*)p = (C32){ c }; // { dg-warning "writing 1 byte into a region of size 0" } + + /* Verify a local variable too. */ + char a32[32]; + p = a32 + 1; + *(C32*)p = (C32){ c }; // { dg-warning "writing 1 byte into a region of size 0" } + sink (p); +} + + +void nowarn_i16_64 (int16_t i) +{ + extern char nowarn_a64[64]; + + void *p = nowarn_a64; + I16_64 *q = (I16_64*)p; + *q = (I16_64){ i }; + + char a64[64]; + q = (I16_64*)a64; + *q = (I16_64){ i }; + sink (q); +} + +void warn_i16_64 (int16_t i) +{ + extern char warn_a64[64]; // { dg-message "at offset 128 to object 'warn_a64' with size 64" "pr97027" { xfail *-*-* } } + + void *p = warn_a64 + 1; + I16_64 *q = (I16_64*)p; + *q = (I16_64){ i }; // { dg-warning "writing 1 byte into a region of size 0" "pr97027" { xfail *-*-* } } + + char a64[64]; + p = a64 + 1; + q = (I16_64*)p; + *q = (I16_64){ i }; // { dg-warning "writing 1 byte into a region of size 0" "pr97027" { xfail *-*-* } } + sink (p); +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c new file mode 100644 index 00000000000..84b6c94fa7e --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c @@ -0,0 +1,146 @@ +/* Verify the handling of anti-ranges/multi-ranges by allocation functions + and subsequent accesses. + { dg-do compile } + { dg-options "-O2" } */ + +typedef __SIZE_TYPE__ size_t; + +void* malloc (size_t); +void bzero (void*, size_t); +void* memset (void*, int, size_t); + + +/* Exercise size_t (via malloc and memset) and unsigned/signed int. */ + +__attribute__ ((alloc_size (1))) void* +alloc_int (int); + +__attribute__ ((access (write_only, 1, 2))) void +access_int (void*, int); + +__attribute__ ((alloc_size (1))) void* +alloc_uint (unsigned); + +__attribute__ ((access (write_only, 1, 2))) void +access_uint (void*, unsigned); + + +void* nowarn_malloc_memset_same_anti_range (size_t n) +{ + /* Set N to the anti-range ~[3, 3]. */ + if (n == 3) + n = 4; + void *p = malloc (n); + + /* Verify there is no warning for an access to N bytes at P. + This means the warning has to assume the value of N in the call + to alloc() is in the larger subrange [4, UINT_MAX], while in + the call to access() in [0, 3]. */ + return memset (p, 0, n); +} + +/* Same as above but with two valid ranges. */ + +void* nowarn_malloc_memset_anti_range (size_t n1, size_t n2) +{ + /* Set N1 to the anti-range ~[3, 3]. */ + if (n1 == 3) + n1 = 4; + void *p = malloc (n1); + + /* Set N2 to the anti-range ~[7, 7]. */ + if (n2 == 7) + n2 = 8; + + return memset (p, 0, n2); +} + + +void nowarn_alloc_access_same_anti_range_int (int n) +{ + /* Set N to the anti-range ~[3, 3]. */ + if (n == 3) + n = 4; + void *p = alloc_int (n); + + /* Verify there is no warning for an access to N bytes at P. + This means the warning has to assume the value of N in the call + to alloc() is in the larger subrange [4, UINT_MAX], while in + the call to access() in [0, 3]. */ + access_int (p, n); +} + +/* Same as above but with two valid ranges. */ + +void nowarn_alloc_access_anti_range_int (int n1, int n2) +{ + /* Set N1 to the anti-range ~[3, 3]. */ + if (n1 == 3) + n1 = 4; + void *p = alloc_int (n1); + + /* Set N2 to the anti-range ~[7, 7]. */ + if (n2 == 7) + n2 = 8; + + access_int (p, n2); +} + + +void nowarn_alloc_access_same_anti_range_uint (unsigned n) +{ + /* Set N to the anti-range ~[3, 3]. */ + if (n == 3) + n = 4; + void *p = alloc_uint (n); + + /* Verify there is no warning for an access to N bytes at P. + This means the warning has to assume the value of N in the call + to alloc() is in the larger subrange [4, UINT_MAX], while in + the call to access() in [0, 3]. */ + access_uint (p, n); +} + +/* Same as above but with two valid ranges. */ + +void nowarn_alloc_access_anti_range_uint (unsigned n1, unsigned n2) +{ + /* Set N1 to the anti-range ~[3, 3]. */ + if (n1 == 3) + n1 = 4; + void *p = alloc_uint (n1); + + /* Set N2 to the anti-range ~[7, 7]. */ + if (n2 == 7) + n2 = 8; + + access_uint (p, n2); +} + + +void* nowarn_malloc_anti_range_memset_range (size_t n1, size_t n2) +{ + /* Set N1 to the anti-range ~[3, 3]. */ + if (n1 == 3) + n1 = 4; + void *p = malloc (n1); + + /* Set N2 to the range [5, MAX]. */ + if (n2 < 5) + n2 = 5; + return memset (p, 0, n2); +} + +void* nowarn_malloc_range_bzero_anti_range (size_t n1, size_t n2) +{ + /* Set N1 to the anti-range ~[3, 3]. */ + if (n1 > 4) + n1 = 4; + void *p = malloc (n1); + + /* Set N2 to the range [5, MAX]. */ + if (n2 <= 3 || 5 <= n2) + n2 = 4; + bzero (p, n2); + return p; +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c new file mode 100644 index 00000000000..7df58e5209e --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c @@ -0,0 +1,125 @@ +/* Verify that writes at excessive offsets into objects of unknown size + pointed to by function arguments are diagnosed. + { dg-do compile } + { dg-options "-O2" } */ + +#define DIFF_MAX __PTRDIFF_MAX__ + +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __SIZE_TYPE__ size_t; + +void* memset (void*, int, size_t); + +void sink (void*); + +char* fcall (void); + +void char_ptr_cst_off_cst_size (char *p) + // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'p'" "note" { target *-*-* } .-1 } +{ + size_t idx = DIFF_MAX - 3; + + memset (p + idx, 0, 3); + sink (p); + + ++idx; + memset (p + idx, 0, 3); // { dg-warning "writing 3 bytes into a region of size 2" } + sink (p); + + ++idx; + memset (p + idx, 0, 3); // { dg-warning "writing 3 bytes into a region of size 1" } + + ++idx; + memset (p + idx, 0, 3); // { dg-warning "writing 3 bytes into a region of size 0" } +} + + +void char_ptr_var_difoff_cst_size (ptrdiff_t idx) +{ + char *p = fcall (); + /* The offset is a range with a very large lower bound and an upper + bound of DIFF_MAX. There's not point in also mentioning the latter + (it wouldn't make the note any more meaningful) so verify it only + mentions the lower bound. + { dg-message "at offset \\d+ into destination object of size \\\[0, \\d+] (allocated|returned) by 'fcall'" "note" { target *-*-* } .-5 } */ + + if (idx < DIFF_MAX - 3) + idx = DIFF_MAX - 3; + + memset (p + idx, 0, 3); + sink (p); + + memset (p + idx, 0, 5); // { dg-warning "writing 5 bytes into a region of size 3" } +} + + +void char_ptr_var_szoff_cst_size (size_t idx) +{ + extern char* gptr; + // { dg-message "at offset \\d+ into destination object 'gptr'" "note" { target *-*-* } .-1 } + + char *p = gptr; + + if (idx < DIFF_MAX - 3) + idx = DIFF_MAX - 3; + + memset (p + idx, 0, 3); + sink (p); + + memset (p + idx, 0, 5); // { dg-warning "writing 5 bytes into a region of size 3" "" { xfail *-*-* } } + + if (idx > DIFF_MAX) + idx = DIFF_MAX; + + memset (p + idx, 0, 7); // { dg-warning "writing 7 bytes into a region of size 3" } +} + + +void char_ptr_var_difoff_var_size (char *p, ptrdiff_t idx, size_t n) + // { dg-message "at offset \\d+ into destination object 'p'" "note" { target *-*-* } .-1 } +{ + if (idx < DIFF_MAX - 3) + idx = DIFF_MAX - 3; + + if (n < 3 || 7 < n) + n = 3; + + memset (p + idx, 0, n); + sink (p); + + ++n; + memset (p + idx, 0, n); // { dg-warning "writing between 4 and 8 bytes into a region of size 3" } +} + + +void char_ptr_var_szoff_var_size (char *p, size_t idx, size_t n) + // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, \[1-9\]\[0-9\]+] into destination object 'p'" "note" { xfail *-*-* } .-1 } +{ + if (idx < DIFF_MAX - 3) + idx = DIFF_MAX - 3; + + if (n < 3 || 7 < n) + n = 3; + + memset (p + idx, 0, n); + sink (p); + + ++n; + /* With an unsigned offset large values are interpreted as negative + so the addition (p + idx) is effectively treated as subtraction, + making an overflow indistinguishable from a valid (if unlikely) + store. */ + memset (p + idx, 0, n); // { dg-warning "writing between 4 and 8 bytes into a region of size 3" "pr?????" { xfail *-*-* } } +} + + +void int_ptr_cst_off_cst_size (int *p) + // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'p'" "note" { target *-*-* } .-1 } +{ + size_t idx = DIFF_MAX / sizeof *p; + + memset (p + idx, 0, 3); + sink (p); + + memset (p + idx, 0, 5); // { dg-warning "writing 5 bytes into a region of size 3" } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c new file mode 100644 index 00000000000..6f36643c8bb --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c @@ -0,0 +1,34 @@ +/* Test case derived from Binutils/GDB's readline/readline/histexpand.c. + { dg-do compile } + { dg-options "-O2 -Wall" } */ + +char * +get_subst_pattern (char *str, int *iptr, int delimiter, int is_rhs, int *lenptr) +{ + int si, i, j, k; + char *s; + + s = 0; + i = *iptr; + + for (si = i; str[si] && str[si] != delimiter; si++) + if (str[si] == '\\' && str[si + 1] == delimiter) + si++; + + if (si > i || is_rhs) + { + s = (char *)__builtin_malloc (si - i + 1); + for (j = 0, k = i; k < si; j++, k++) + { + /* Remove a backslash quoting the search string delimiter. */ + if (str[k] == '\\' && str[k + 1] == delimiter) + k++; + s[j] = str[k]; // { dg-bogus "-Wstringop-overflow" } + } + s[j] = '\0'; + if (lenptr) + *lenptr = j; + } + + return s; +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c new file mode 100644 index 00000000000..a28965557c3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c @@ -0,0 +1,62 @@ + +/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy + Verify that writes by built-in functions to objects through pointers + returned by ordinary (non-built-int) function are assumed to point to + the beginning of objects. + { dg-do compile } + { dg-options "-O2" } */ + +#include "range.h" + +void* memcpy (void*, const void*, size_t); +void* memset (void*, int, size_t); + +void sink (void*, ...); + +extern char* arrptr[]; +extern char* ptr; +extern char* retptr (void); +struct S { char *p; }; +extern struct S retstruct (void); + +void nowarn_ptr (void) +{ + { + void *p = arrptr; + memset (p - 1, 0, 12345); // { dg-warning "\\\[-Wstringop-overflow" } + memset (p,0, 12345); + memset (p,0, DIFF_MAX - 1); + } + + { + char *p = arrptr[0]; + memset (p - 1, 0, 12345); + memset (p - 12345, 0, 12345); + memset (p - 1234, 0, DIFF_MAX - 1); + memset (p - DIFF_MAX + 1, 0, 12345); + } + + { + char *p = ptr; + memset (p - 1, 0, 12345); + memset (p - 12345, 0, 12345); + memset (p - 1234, 0, DIFF_MAX - 1); + memset (p - DIFF_MAX + 1, 0, 12345); + } + + { + char *p = retptr (); + memset (p - 1, 0, 12345); + memset (p - 12345, 0, 12345); + memset (p - 1234, 0, DIFF_MAX - 1); + memset (p - DIFF_MAX + 1, 0, 12345); + } + + { + char *p = retstruct ().p; + memset (p - 1, 0, 12345); + memset (p - 12345, 0, 12345); + memset (p - 1234, 0, DIFF_MAX - 1); + memset (p - DIFF_MAX + 1, 0, 12345); + } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-53.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-53.c new file mode 100644 index 00000000000..cd8fa3202eb --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-53.c @@ -0,0 +1,116 @@ +/* PR middle-end/96384 - bogus -Wstringop-overflow= storing into + multidimensional array with index in range + { dg-do compile } + { dg-options "-O2 -Wall" } */ + +#define SHRT_MAX __SHRT_MAX__ +#define SHRT_MIN (-SHRT_MAX - 1) +#define INT_MAX __INT_MAX__ +#define INT_MIN (-INT_MAX - 1) +#define LONG_MAX __LONG_MAX__ +#define LONG_MIN (-LONG_MAX - 1) + +#define USHRT_MAX (SHRT_MAX * 2 + 1) +#define UINT_MAX ~0U +#define ULONG_MAX ~0LU + +char ca3_5_7[3][5][7]; + +void nowarn_ca_3_5_ssi (short i) +{ + if (i > SHRT_MAX - 1) + i = SHRT_MAX - 1; + + ca3_5_7[i][0][0] = __LINE__; + ca3_5_7[i][0][1] = __LINE__; + ca3_5_7[i][0][2] = __LINE__; + ca3_5_7[i][0][3] = __LINE__; + ca3_5_7[i][0][4] = __LINE__; + ca3_5_7[i][0][5] = __LINE__; + ca3_5_7[i][0][6] = __LINE__; + + ca3_5_7[i][1][0] = __LINE__; + ca3_5_7[i][1][1] = __LINE__; + ca3_5_7[i][1][2] = __LINE__; + ca3_5_7[i][1][3] = __LINE__; + ca3_5_7[i][1][4] = __LINE__; + ca3_5_7[i][1][5] = __LINE__; + ca3_5_7[i][1][6] = __LINE__; + + ca3_5_7[i][2][0] = __LINE__; + ca3_5_7[i][2][1] = __LINE__; + ca3_5_7[i][2][2] = __LINE__; + ca3_5_7[i][2][3] = __LINE__; + ca3_5_7[i][2][4] = __LINE__; + ca3_5_7[i][2][5] = __LINE__; + ca3_5_7[i][2][6] = __LINE__; + + ca3_5_7[i][3][0] = __LINE__; + ca3_5_7[i][3][1] = __LINE__; + ca3_5_7[i][3][2] = __LINE__; + ca3_5_7[i][3][3] = __LINE__; + ca3_5_7[i][3][4] = __LINE__; + ca3_5_7[i][3][5] = __LINE__; + ca3_5_7[i][3][6] = __LINE__; + + ca3_5_7[i][4][0] = __LINE__; + ca3_5_7[i][4][1] = __LINE__; + ca3_5_7[i][4][2] = __LINE__; + ca3_5_7[i][4][3] = __LINE__; + ca3_5_7[i][4][4] = __LINE__; + ca3_5_7[i][4][5] = __LINE__; + ca3_5_7[i][4][6] = __LINE__; + + ca3_5_7[1][i][5] = __LINE__; + ca3_5_7[2][3][i] = __LINE__; +} + +void nowarn_ca_3_5_usi (unsigned short i) +{ + if (i > USHRT_MAX - 1) + i = USHRT_MAX - 1; + + ca3_5_7[i][3][5] = __LINE__; + ca3_5_7[1][i][5] = __LINE__; + ca3_5_7[2][3][i] = __LINE__; +} + +void nowarn_ca_3_5_si (int i) +{ + if (i > INT_MAX - 1) + i = INT_MAX - 1; + + ca3_5_7[i][3][5] = __LINE__; + ca3_5_7[1][i][5] = __LINE__; + ca3_5_7[2][3][i] = __LINE__; +} + +void nowarn_ca_3_5_ui (unsigned i) +{ + if (i > UINT_MAX - 1) + i = UINT_MAX - 1; + + ca3_5_7[i][3][5] = __LINE__; + ca3_5_7[1][i][5] = __LINE__; + ca3_5_7[2][3][i] = __LINE__; +} + +void nowarn_ca_3_5_li (long i) +{ + if (i > LONG_MAX - 1) + i = LONG_MAX - 1; + + ca3_5_7[i][3][5] = __LINE__; + ca3_5_7[1][i][5] = __LINE__; + ca3_5_7[2][3][i] = __LINE__; +} + +void nowarn_ca_3_5_uli (unsigned long i) +{ + if (i > ULONG_MAX - 1) + i = ULONG_MAX - 1; + + ca3_5_7[i][3][5] = __LINE__; + ca3_5_7[1][i][5] = __LINE__; + ca3_5_7[2][3][i] = __LINE__; +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-54.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-54.c new file mode 100644 index 00000000000..26568f8366d --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-54.c @@ -0,0 +1,103 @@ +/* Verify that writes at excessive offsets into flexible array members + of extern or allocated objects of unknow size are diagnosed. + { dg-do compile } + { dg-options "-O2" } */ + +#define DIFF_MAX __PTRDIFF_MAX__ + +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __SIZE_TYPE__ size_t; + +void* memset (void*, int, size_t); + +void sink (void*); + +void char_flexarray_cst_off_cst_size (void) +{ + extern struct { char n, a[]; } + caxcc; // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'caxcc'" } + + char *p = caxcc.a; + size_t idx = DIFF_MAX - 4; + + memset (p + idx, 0, 3); + sink (p); + + ++idx; + memset (p + idx, 0, 3); // { dg-warning "writing 3 bytes into a region of size 2" } + sink (p); + + ++idx; + memset (p + idx, 0, 3); // { dg-warning "writing 3 bytes into a region of size 1" } + + ++idx; + memset (p + idx, 0, 3); // { dg-warning "writing 3 bytes into a region of size 0" } +} + + +void char_flexarray_var_off_cst_size (ptrdiff_t idx) +{ + extern struct { char n, a[]; } + caxvc; // { dg-message "destination object 'caxvc'" } + + char *p = caxvc.a; + + if (idx < DIFF_MAX - 4) + idx = DIFF_MAX - 4; + + memset (p + idx, 0, 3); + sink (p); + + memset (p + idx, 0, 5); // { dg-warning "writing 5 bytes into a region of size 3" } +} + + +void char_flexarray_var_off_var_size (size_t n, ptrdiff_t idx) +{ + extern struct { char n, a[]; } + caxvv; // { dg-message "destination object 'caxvv'" } + + char *p = caxvv.a; + + if (idx < DIFF_MAX - 4) + idx = DIFF_MAX - 4; + + if (n < 3 || 7 < n) + n = 3; + + memset (p + idx, 0, n); + sink (p); + + ++n; + memset (p + idx, 0, n); // { dg-warning "writing between 4 and 8 bytes into a region of size 3" } +} + + +void alloc_array_var_off_cst_size (size_t n, ptrdiff_t idx) +{ + struct { char n, a[]; } + *p = __builtin_malloc (n); // { dg-message "at offset \\d+ into destination object" } + + if (idx < DIFF_MAX - 4) + idx = DIFF_MAX - 4; + + memset (p->a + idx, 0, 3); + sink (p); + + memset (p->a + idx, 0, 5); // { dg-warning "writing 5 bytes into a region of size 3" } +} + + +void int_array_cst_off_cst_size (void) +{ + extern struct { int n, a[]; } + iaxc; // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'iaxc'" } + + int *p = iaxc.a; + size_t idx = DIFF_MAX / sizeof *p - 1; + + memset (p + idx, 0, 3); + sink (p); + + memset (p + idx, 0, 5); // { dg-warning "writing 5 bytes into a region of size 3" } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-55.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-55.c new file mode 100644 index 00000000000..25f5b82d9be --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-55.c @@ -0,0 +1,97 @@ +/* Verify that offsets in "anti-ranges" are handled correctly. + { dg-do compile } + { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */ + +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __SIZE_TYPE__ size_t; + +void* memset (void*, int, size_t); + +void sink (void*, ...); +#define T(x) sink (x) + + +void int_range_add_sub_ (int i, int j) +{ + if (i < 1) i = 1; + if (j > -1) j = -1; + + char ca5[5]; // { dg-message "at offset \\\[1, 5]" "note" } + char *p0 = ca5; // offset + char *p1 = p0 + i; // 1-5 + char *p2 = p1 + i; // 2-5 + char *p3 = p2 + j; // 0-4 + char *p4 = p3 + j; // 0-3 + char *p5 = p4 + j; // 0-2 + char *p6 = p5 + j; // 0-1 + char *p7 = p6 + i; // 1-2 + + memset (p7, 0, 5); // { dg-warning "writing 5 bytes into a region of size 4" } + + sink (p0, p1, p2, p3, p4, p5, p6, p7); +} + + +void ruint_arint_add (unsigned i, int j) +{ + i |= 1; // [1, UINT_MAX] + j |= 1; // [INT_MIN + 1, -1] U [1, INT_MAX] + + char a[5]; // { dg-message "at offset \\\[1, 5]" "note" } + char *p0 = a; // offset + char *p1 = p0 + i; // 1-5 + T (memset (p1, 0, 4)); + + char *p2 = p1 + j; // 0-5 + T (memset (p2, 0, 5)); + + char *p3 = p2 + i; // 1-5 + T (memset (p3, 0, 4)); + + char *p4 = p3 + j; // 0-5 + T (memset (p4, 0, 5)); + + char *p5 = p4 + i; // 1-5 + T (memset (p5, 0, 4)); + + char *p6 = p5 + j; // 0-5 + T (memset (p6, 0, 5)); + + char *p7 = p6 + i; // 1-5 + T (memset (p7, 0, 5)); // { dg-warning "writing 5 bytes into a region of size 4" "" } +} + + +void warn_ptrdiff_anti_range_add (ptrdiff_t i) +{ + i |= 1; + + char ca5[5]; // { dg-message "at offset \\\[1, 5]" "pr?????" { xfail *-*-* } } + char *p0 = ca5; // offset + char *p1 = p0 + i; // 1-5 + char *p2 = p1 + i; // 2-5 + char *p3 = p2 + i; // 3-5 + char *p4 = p3 + i; // 4-5 + char *p5 = p4 + i; // 5 + + memset (p5, 0, 5); // { dg-warning "writing 5 bytes into a region of size 0" "pr?????" { xfail *-*-* } } + + sink (p0, p1, p2, p3, p4, p5); +} + +void warn_int_anti_range (int i) +{ + i |= 1; + + char ca5[5]; // { dg-message "at offset \\\[1, 5]" "pr?????" { xfail *-*-* } } + char *p0 = ca5; // offset + char *p1 = p0 + i; // 1-5 + char *p2 = p1 + i; // 2-5 + char *p3 = p2 + i; // 3-5 + char *p4 = p3 + i; // 4-5 + char *p5 = p4 + i; // 5 + + memset (p5, 0, 5); // { dg-warning "writing 5 bytes into a region of size 0" "pr?????" { xfail *-*-* } } + + sink (p0, p1, p2, p3, p4, p5); +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overread-5.c b/gcc/testsuite/gcc.dg/Wstringop-overread-5.c new file mode 100644 index 00000000000..b75002b4295 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overread-5.c @@ -0,0 +1,76 @@ +/* Verify -Wstringop-overread with a source pointer pointing either + before the beginning or past the end of an object. + { dg-do compile } + { dg-options "-O -Wall -Wno-array-bounds" } */ + +typedef __SIZE_TYPE__ size_t; + +size_t strlen (const char *); + +void sink (void*, ...); + +void off_sz_or_1 (size_t i) +{ + i |= 1; + + /* Verify the offset in the notes only mentions the meaningful lower + bound and not a range with the excessive (and meaningless) upper + bound like [2, 9223372036854775807]. */ + extern char a[1]; + // { dg-message "at offset 1 into source object 'a'" "note" { target *-*-* } .-1 } + // { dg-message "at offset 2 " "note" { target *-*-* } .-2 } + + char *p1 = a + i; + char *p2 = p1 + 1; + char *p3 = p1 - 1; + + size_t n = 0; + n += strlen (p1); // { dg-warning "reading 1 or more bytes from a region of size 0" } + n += strlen (p2); // { dg-warning "reading 1 or more bytes from a region of size 0" } + n += strlen (p3); + + sink (p1, p2, p3, n); +} + + +void off_sz_or_2 (size_t i) +{ + i |= 2; + + extern char b[2]; + // { dg-message "at offset 2 " "note" { target *-*-* } .-1 } + // { dg-message "at offset 3 " "note" { target *-*-* } .-2 } + + char *p1 = b + i; + char *p2 = p1 + 1; + char *p3 = p1 - 1; + + size_t n = 0; + n += strlen (p1); // { dg-warning "reading 1 or more bytes from a region of size 0" } + n += strlen (p2); // { dg-warning "reading 1 or more bytes from a region of size 0" } + n += strlen (p3); + + sink (p1, p2, p3, n); +} + + +void off_sz_or_4 (size_t i) +{ + i |= 4; + + extern char c[3]; + // { dg-message "at offset 4 " "note" { target *-*-* } .-1 } + // { dg-message "at offset 5 " "note" { target *-*-* } .-2 } + // { dg-message "at offset 3 " "note" { target *-*-* } .-3 } + + char *p1 = c + i; + char *p2 = p1 + 1; + char *p3 = p1 - 1; + + size_t n = 0; + n += strlen (p1); // { dg-warning "reading 1 or more bytes from a region of size 0" } + n += strlen (p2); // { dg-warning "reading 1 or more bytes from a region of size 0" } + n += strlen (p3); // { dg-warning "reading 1 or more bytes from a region of size 0" } + + sink (p1, p2, p3, n); +} diff --git a/gcc/testsuite/gcc.dg/pr51683.c b/gcc/testsuite/gcc.dg/pr51683.c index c477cd8fdd2..7a624e4055d 100644 --- a/gcc/testsuite/gcc.dg/pr51683.c +++ b/gcc/testsuite/gcc.dg/pr51683.c @@ -12,6 +12,9 @@ void * foo (void *p) { return bar ((void *) 0x12345000, p, 256); + /* Integers converted to pointers are assumed to be the result of + (invalid) arithmetic on null pointers. + { dg-prune-output "writing 256 bytes into a region of size 0" } */ } /* { dg-final { scan-tree-dump "memcpy" "optimized" } } */ diff --git a/gcc/testsuite/gcc.target/i386/pr60693.c b/gcc/testsuite/gcc.target/i386/pr60693.c index e6033a783f4..962c705f18f 100644 --- a/gcc/testsuite/gcc.target/i386/pr60693.c +++ b/gcc/testsuite/gcc.target/i386/pr60693.c @@ -11,3 +11,6 @@ foo (void) __builtin_memcpy (buf, (void *) 0x8000, 4096); bar (buf); } + +/* Reading from a constant address might triggers: + { dg-prune-output "\\\[-Wstringop-overread" } */ diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index f4d1c5ca256..ebb17cd852c 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -228,8 +228,20 @@ get_range (tree val, gimple *stmt, wide_int minmax[2], value_range_kind rng = get_range_info (val, minmax, minmax + 1); if (rng == VR_RANGE) + /* This may be an inverted range whose MINMAX[1] < MINMAX[0]. */ return val; + if (rng == VR_ANTI_RANGE) + { + /* An anti-range is the same as an ordinary range with inverted + bounds (where MINMAX[1] < MINMAX[0] is true) that may result + from the conversion of a signed anti-range to unsigned. */ + wide_int tmp = minmax[0]; + minmax[0] = minmax[1] + 1; + minmax[1] = wi::sub (tmp, 1); + 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 NULL_TREE; @@ -2243,7 +2255,7 @@ maybe_warn_overflow (gimple *stmt, tree len, sprintf (offstr, "[%lli, %lli]", (long long) offrng[0].to_shwi (), (long long) offrng[1].to_shwi ()); - if (destdecl) + if (destdecl && DECL_P (destdecl)) { if (tree size = DECL_SIZE_UNIT (destdecl)) inform (DECL_SOURCE_LOCATION (destdecl), -- 2.30.2