Generalize compute_objsize to return maximum size/offset instead of failing. Also resolves: PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy gcc/ChangeLog: PR middle-end/97023 * 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): Update comment. (compute_objsize): 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. gcc/testsuite/ChangeLog: PR middle-end/97023 * c-c++-common/Wrestrict.c: Add new case, expect warning for existing. * gcc.dg/pr51683.c: Prune out expected warning. * gcc.dg/Wstringop-overflow-41.c: New test. * gcc.dg/Wstringop-overflow-44.c: 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. commit 7b10cb93b9bf5c38f4805767957506ca94a32fae Author: Martin Sebor Date: Fri Sep 11 16:39:05 2020 -0600 compute_objsize generalization. diff --git a/gcc/builtins.c b/gcc/builtins.c index 611cf7f1f4b..9e5e6ce10da 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -199,7 +199,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; @@ -214,7 +214,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]); @@ -222,6 +222,83 @@ access_ref::access_ref (tree bound /* = NULL_TREE */, } } +offset_int +access_ref::size_remaining (offset_int *pmin /* = NULL */) const +{ + offset_int minbuf; + if (!pmin) + pmin = &minbuf; + + 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 (offrng[1] < offrng[0]) + { + if (offrng[1] < 0) + { + *pmin = 0; + return offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0; + } + + *pmin = offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0; + return sizrng[1]; + } + + if (sizrng[1] <= offrng[0]) + { + /* If the starting offset is greater than the upper bound on + the size of the object the space remaining is zero. */ + *pmin = 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 (offrng[1] < offrng[0]) + { + if (offrng[1] < 0) + { + *pmin = sizrng[0]; + return sizrng[1]; + } + + if (offrng[0] < sizrng[1]) + *pmin = offrng[0] < 0 ? sizrng[1] : sizrng[1] - offrng[0]; + else + *pmin = 0; + + return sizrng[1]; + } + + 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; +} + /* Return true if NAME starts with __builtin_ or __sync_. */ static bool @@ -3634,23 +3711,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 %lli into destination object %qE", minoff, ref.ref); } else - inform (loc, "at offset [%lli, %lli] into destination object %qD", + inform (loc, "at offset [%lli, %lli] into destination object %qE", minoff, maxoff, ref.ref); return; } @@ -4121,8 +4202,14 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, if (!rng1) rng1 = rng1_buf; - if (!get_range (size, rng1, rvals)) - return NULL_TREE; + { + 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]); + rng1[1] = wi::to_wide (r[1]); + } if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST) return fold_convert (sizetype, size); @@ -4195,6 +4282,82 @@ get_range (tree x, 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]) +{ + 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_range (off, SIGNED, offrng)) + { + 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_range (off, SIGNED, offrng)) + 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). @@ -4204,7 +4367,8 @@ get_range (tree x, 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. */ @@ -4221,25 +4385,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); @@ -4247,27 +4426,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; @@ -4323,8 +4504,10 @@ 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, SIGNED, orng, rvals)) - /* Fail unless the size of the object is zero. */ - return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1]; + { + orng[1] = wi::to_offset (max_object_size ()); + orng[0] = -orng[1] - 1; + } if (TREE_CODE (ptr) == ARRAY_REF) { @@ -4343,7 +4526,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; @@ -4375,24 +4561,56 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, return true; } - else if (code == POINTER_PLUS_EXPR) + + if (code == TARGET_MEM_REF) { tree ref = TREE_OPERAND (ptr, 0); if (!compute_objsize (ref, ostype, pref, visited, rvals)) return false; - offset_int orng[2]; - tree off = pref->eval (TREE_OPERAND (ptr, 1)); - if (!get_range (off, SIGNED, orng, rvals)) - /* Fail unless the size of the object is zero. */ - return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1]; + /* TODO: Handle remaining operands. Until then, add maximum offset. */ + pref->ref = ptr; + pref->add_max_offset (); + return true; + } - pref->offrng[0] += orng[0]; - pref->offrng[1] += orng[1]; + 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; } - else if (code == VIEW_CONVERT_EXPR) + + if (code == STRING_CST) + { + pref->sizrng[0] = pref->sizrng[1] = TREE_STRING_LENGTH (ptr); + return true; + } + + if (code == POINTER_PLUS_EXPR) + { + tree ref = TREE_OPERAND (ptr, 0); + if (!compute_objsize (ref, ostype, pref, visited, rvals)) + return false; + + offset_int orng[2]; + tree off = pref->eval (TREE_OPERAND (ptr, 1)); + if (get_range (off, SIGNED, orng, rvals)) + pref->add_offset (orng[0], orng[1]); + else + pref->add_max_offset (); + return true; + } + + if (code == VIEW_CONVERT_EXPR) { ptr = TREE_OPERAND (ptr, 0); return compute_objsize (ptr, ostype, pref, visited, rvals); @@ -4404,32 +4622,77 @@ 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)) + { + 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; } /* 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 @@ -4438,32 +4701,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, tree off = gimple_assign_rhs2 (stmt); if (!get_range (off, 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)); + orng[0] = -wi::to_offset (max_object_size ()) - 1; + orng[1] = wi::to_offset (max_object_size ()); } - pref->offrng[0] += orng[0]; - pref->offrng[1] += orng[1]; + pref->add_offset (orng[0], orng[1]); + return compute_objsize (ptr, ostype, pref, visited, rvals); } - 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, 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 @@ -4484,27 +4743,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 @@ -4518,7 +4760,7 @@ compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */, 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) @@ -5799,13 +6041,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 e42c9664724..9b06fd5ebee 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -183,11 +183,42 @@ struct access_ref values. */ 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; + + void set_max_size_range () + { + sizrng[0] = 0; + sizrng[1] = wi::to_offset (max_object_size ()); + } + + void add_offset (const offset_int &off) + { + offrng[0] += off; + offrng[1] += off; + } + + void add_offset (const offset_int &min, const offset_int &max) + { + offrng[0] += min; + offrng[1] += max; + } + + void add_max_offset () + { + offrng[0] -= wi::to_offset (max_object_size ()) + 1; + offrng[1] += wi::to_offset (max_object_size ()); + } + /* 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 diff --git a/gcc/calls.c b/gcc/calls.c index 8ac94db6817..dabbb5b442d 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -1241,14 +1241,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; @@ -1320,25 +1322,42 @@ 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) + && wi::ltu_p (maxsize, max)) + min = max = wi::zero (expprec); + 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/cp/decl.c b/gcc/cp/decl.c index ad2a30fcf71..62ac775dc95 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -17460,10 +17460,10 @@ complete_vars (tree type) && (TYPE_MAIN_VARIANT (strip_array_types (type)) == iv->incomplete_type)) { - /* Complete the type of the variable. The VAR_DECL itself - will be laid out in expand_expr. */ + /* Complete the type of the variable. */ complete_type (type); cp_apply_type_quals_to_decl (cp_type_quals (type), var); + layout_decl (var, 0); } /* Remove this entry from the list. */ diff --git a/gcc/testsuite/c-c++-common/Wrestrict.c b/gcc/testsuite/c-c++-common/Wrestrict.c index 3b019c8a80e..7a536456c47 100644 --- a/gcc/testsuite/c-c++-common/Wrestrict.c +++ b/gcc/testsuite/c-c++-common/Wrestrict.c @@ -320,14 +320,16 @@ 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. */ - T (d, s, UAR (1, DIFF_MAX - 1)); + /* 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] and assuming + a nonzero size, 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 so such a large object cannot exist. */ + T (d, s, UAR (1, DIFF_MAX / 2 - 1)); + T (d, s, UAR (1, DIFF_MAX - 1)); /* { dg-warning "\\\[-Wrestrict" } */ 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/gcc.dg/Wstringop-overflow-41.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c new file mode 100644 index 00000000000..2647043b1ef --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c @@ -0,0 +1,118 @@ +/* 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 \[1-9\]\[0-9\]+ 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 upper bound of the offset would ideally be positive but for now + allow it to be negative too. */ + extern char caxvc[]; // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'caxvc'" } + + 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 \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'caxvv'" } + + 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 \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object" } + + 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-44.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c new file mode 100644 index 00000000000..a9cf4071fff --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.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 \[1-9\]\[0-9\]+ into destination object" "" { xfail *-*-* } } + + 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-45.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c new file mode 100644 index 00000000000..7e9be57ae85 --- /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" } + 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, 14] into destination object of size 11 " } + 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 " } + 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 " } + 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..68c9c866887 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c @@ -0,0 +1,94 @@ +/* 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 upper bound of the offset in last + note is bigger because it includes the upper bound of the offset + of the pointer returned from the previous memchr() call. */ + 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, 12] 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..ca98286f449 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c @@ -0,0 +1,121 @@ +/* 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 (); + // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object of size \\\[0, \[1-9\]\[0-9\]+] (allocated|returned) by 'fcall'" "note" { target *-*-* } .-1 } + + 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 \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] 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 \\\[\[1-9\]\[0-9\]+, \[1-9\]\[0-9\]+] 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/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" } } */