The attached patch extends compute_objsize() to handle conditional expressions represented either as PHIs or MIN_EXPR and MAX_EXPR.
To simplify the handling of the -Wstringop-overflow/-overread warnings the change factors this code out of tree-ssa-strlen.c and into inform_access() in builtins.c, making it a member of access_ref. Besides eliminating a decent amount of code duplication this also improves the consistency of the warnings. Finally, the change introduces a distinction between the definite kinds of -Wstringop-overflow (and -Wstringop-overread) warnings and the maybe kind. The latter are currently only being issued for function array parameters but I expect to make use of them more extensively in the future. Besides the usual GCC bootstrap/regtest I have tested the change with Binutils/GDB and Glibc and verified that it doesn't introduce any false positives. Martin
PR middle-end/92936 - missing warning on a past-the-end store to a PHI PR middle-end/92940 - incorrect offset and size in -Wstringop-overflow for out-of-bounds store into VLA and two offset ranges PR middle-end/89428 - missing -Wstringop-overflow on a PHI with variable offset gcc/ChangeLog: PR middle-end/92936 PR middle-end/92940 PR middle-end/89428 * builtins.c (access_ref::access_ref): Initialize member. (access_ref::phi): New function. (access_ref::get_ref): New function. (access_ref::add_offset): Remove duplicate assignment. (maybe_warn_for_bound): Add "maybe" kind of warning messages. (warn_for_access): Same. (inform_access): Rename... (access_ref::inform_access): ...to this. Print PHI arguments. Format offset the same as size and simplify. Improve printing of allocation functions and VLAs. (check_access): Adjust to the above. (gimple_parm_array_size): Change argument. (handle_min_max_size): New function. * builtins.h (struct access_ref): Declare new members. (gimple_parm_array_size): Change argument. * tree-ssa-strlen.c (maybe_warn_overflow): Use access_ref and simplify. (handle_builtin_memcpy): Correct argument passed to maybe_warn_overflow. (handle_builtin_memset): Same. gcc/testsuite/ChangeLog: PR middle-end/92936 PR middle-end/92940 PR middle-end/89428 * c-c++-common/Wstringop-overflow-2.c: Adjust text of expected informational notes. * gcc.dg/Wstringop-overflow-11.c: Remove xfails. * gcc.dg/Wstringop-overflow-12.c: Same. * gcc.dg/Wstringop-overflow-17.c: Adjust text of expected messages. * gcc.dg/Wstringop-overflow-27.c: Same. Remove xfails. * gcc.dg/Wstringop-overflow-28.c: Adjust text of expected messages. * gcc.dg/Wstringop-overflow-29.c: Same. * gcc.dg/Wstringop-overflow-37.c: Same. * gcc.dg/Wstringop-overflow-46.c: Same. * gcc.dg/Wstringop-overflow-47.c: Same. * gcc.dg/Wstringop-overflow-54.c: Same. * gcc.dg/warn-strnlen-no-nul.c: Add expected warning. * gcc.dg/Wstringop-overflow-58.c: New test. * gcc.dg/Wstringop-overflow-59.c: New test. * gcc.dg/Wstringop-overflow-60.c: New test. * gcc.dg/Wstringop-overflow-61.c: New test. * gcc.dg/Wstringop-overflow-62.c: New test. diff --git a/gcc/builtins.c b/gcc/builtins.c index da25343beb1..5d60eab6ba2 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -185,6 +185,7 @@ 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 check_read_access (tree, tree, tree = NULL_TREE, int = 1); +static bool compute_objsize (tree, int, access_ref *, bitmap *, range_query *); unsigned HOST_WIDE_INT target_newline; unsigned HOST_WIDE_INT target_percent; @@ -199,7 +200,8 @@ 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), base0 (true) +: ref (), eval ([](tree x){ return x; }), trail1special (true), base0 (true), + parmarray () { /* Set to valid. */ offrng[0] = offrng[1] = 0; @@ -222,6 +224,131 @@ access_ref::access_ref (tree bound /* = NULL_TREE */, } } +/* Return the PHI node REF refers to or null if it doesn't. */ + +gphi * +access_ref::phi () const +{ + if (!ref || TREE_CODE (ref) != SSA_NAME) + return NULL; + + gimple *def_stmt = SSA_NAME_DEF_STMT (ref); + if (gimple_code (def_stmt) != GIMPLE_PHI) + return NULL; + + return as_a <gphi *> (def_stmt); +} + +/* Determine and return the largest object to which REF refers. If REF + refers to a PHI and PREF is nonnull, fill *PREF with the details of + the object determined by compute_objsize(ARG, OSTYPE) for each PHI + argument ARG. */ + +tree +access_ref::get_ref (vec<access_ref> *all_refs, + access_ref *pref /* = NULL */, + int ostype /* = 1 */, + bitmap *visited /* = NULL */, + range_query *rvals /* = NULL */) const +{ + gphi *phi_stmt = this->phi (); + if (!phi_stmt) + return ref; + + bitmap visited_phis = NULL; + if (!visited) + visited = &visited_phis; + + if (!*visited) + *visited = BITMAP_ALLOC (NULL); + + if (!bitmap_set_bit (*visited, SSA_NAME_VERSION (ref))) + /* Have compute_objsize() fail and propagate the failure below. */ + return NULL_TREE; + + access_ref phi_ref; + if (pref) + phi_ref = *pref; + + /* Set if any argument is a function array (or VLA) parameter not + declared [static]. */ + bool parmarray = false; + /* The size of the smallest object referenced by the PHI arguments. */ + offset_int minsize = 0; + const offset_int maxobjsize = wi::to_offset (max_object_size ()); + const offset_int orng[2] = { phi_ref.offrng[0], phi_ref.offrng[1] }; + + const unsigned nargs = gimple_phi_num_args (phi_stmt); + for (unsigned i = 0; i < nargs; ++i) + { + access_ref phi_arg_ref; + tree arg = gimple_phi_arg_def (phi_stmt, i); + if (!compute_objsize (arg, ostype, &phi_arg_ref, visited, rvals)) + return NULL_TREE; + + /* The call to compute_objsize() above will fail when the ARG has + already been VISITED. */ + gcc_checking_assert (phi_arg_ref.sizrng[0] >= 0); + + /* Add PREF's offset to that of the argument. */ + phi_arg_ref.add_offset (orng[0], orng[1]); + + if (all_refs) + all_refs->safe_push (phi_arg_ref); + + const bool arg_known_size = (phi_arg_ref.sizrng[0] != 0 + || phi_arg_ref.sizrng[1] != maxobjsize); + + parmarray |= phi_arg_ref.parmarray; + + if (phi_ref.sizrng[0] < 0) + { + phi_ref = phi_arg_ref; + if (arg_known_size) + minsize = phi_arg_ref.sizrng[0]; + continue; + } + + const bool phi_known_size = (phi_ref.sizrng[0] != 0 + || phi_ref.sizrng[1] != maxobjsize); + + if (phi_known_size && phi_arg_ref.sizrng[0] < minsize) + minsize = phi_arg_ref.sizrng[0]; + + /* Determine the amount of remaining space in the argument. */ + offset_int argrem[2]; + argrem[1] = phi_arg_ref.size_remaining (argrem); + + /* Determine the amount of remaining space computed so far and + if the remaining space in the argument is more use it instead. */ + offset_int phirem[2]; + phirem[1] = phi_ref.size_remaining (phirem); + + if (phirem[1] < argrem[1]) + /* Use the argument with the most space remaining as the result. */ + phi_ref = phi_arg_ref; + } + + if (phi_ref.sizrng[0] < 0) + /* Fail if none of the PHI's arguments resulted in updating PHI_REF + (perhaps because they have all been already visited by prior + recursive calls). */ + return NULL_TREE; + + /* Replace the lower bound of the largest argument with the size of + the smallest argument, and set PARMARRAY if any argument was one. */ + phi_ref.sizrng[0] = minsize; + phi_ref.parmarray = parmarray; + + if (visited == &visited_phis) + BITMAP_FREE (visited_phis); + + if (pref && pref != this) + *pref = phi_ref; + + return phi_ref.ref; +} + /* Return the maximum amount of space remaining and if non-null, set argument to the minimum. */ @@ -318,7 +445,6 @@ void access_ref::add_offset (const offset_int &min, const offset_int &max) return; } - offrng[1] = maxoff; offset_int absmax = wi::abs (max); if (offrng[0] < absmax) { @@ -3560,28 +3686,42 @@ maybe_warn_for_bound (int opt, location_t loc, tree exp, tree func, if (opt == OPT_Wstringop_overread) { + bool maybe = pad && pad->src.phi (); + if (tree_int_cst_lt (maxobjsize, bndrng[0])) { if (bndrng[0] == bndrng[1]) warned = (func ? warning_at (loc, opt, - "%K%qD specified bound %E " - "exceeds maximum object size %E", + (maybe + ? G_("%K%qD specified bound %E may " + "exceed maximum object size %E") + : G_("%K%qD specified bound %E " + "exceeds maximum object size %E")), exp, func, bndrng[0], maxobjsize) : warning_at (loc, opt, - "%Kspecified bound %E " - "exceeds maximum object size %E", + (maybe + ? G_("%Kspecified bound %E may " + "exceed maximum object size %E") + : G_("%Kspecified bound %E " + "exceeds maximum object size %E")), exp, bndrng[0], maxobjsize)); else warned = (func ? warning_at (loc, opt, - "%K%qD specified bound [%E, %E] " - "exceeds maximum object size %E", + (maybe + ? G_("%K%qD specified bound [%E, %E] may " + "exceed maximum object size %E") + : G_("%K%qD specified bound [%E, %E] " + "exceeds maximum object size %E")), exp, func, bndrng[0], bndrng[1], maxobjsize) : warning_at (loc, opt, - "%Kspecified bound [%E, %E] " - "exceeds maximum object size %E", + (maybe + ? G_("%Kspecified bound [%E, %E] may " + "exceed maximum object size %E") + : G_("%Kspecified bound [%E, %E] " + "exceeds maximum object size %E")), exp, bndrng[0], bndrng[1], maxobjsize)); } else if (!size || tree_int_cst_le (bndrng[0], size)) @@ -3589,22 +3729,34 @@ maybe_warn_for_bound (int opt, location_t loc, tree exp, tree func, else if (tree_int_cst_equal (bndrng[0], bndrng[1])) warned = (func ? warning_at (loc, opt, - "%K%qD specified bound %E exceeds " - "source size %E", + (maybe + ? G_("%K%qD specified bound %E may exceed " + "source size %E") + : G_("%K%qD specified bound %E exceeds " + "source size %E")), exp, func, bndrng[0], size) : warning_at (loc, opt, - "%Kspecified bound %E exceeds " - "source size %E", + (maybe + ? G_("%Kspecified bound %E may exceed " + "source size %E") + : G_("%Kspecified bound %E exceeds " + "source size %E")), exp, bndrng[0], size)); else warned = (func ? warning_at (loc, opt, - "%K%qD specified bound [%E, %E] exceeds " - "source size %E", + (maybe + ? G_("%K%qD specified bound [%E, %E] may " + "exceed source size %E") + : G_("%K%qD specified bound [%E, %E] exceeds " + "source size %E")), exp, func, bndrng[0], bndrng[1], size) : warning_at (loc, opt, - "%Kspecified bound [%E, %E] exceeds " - "source size %E", + (maybe + ? G_("%Kspecified bound [%E, %E] may exceed " + "source size %E") + : G_("%Kspecified bound [%E, %E] exceeds " + "source size %E")), exp, bndrng[0], bndrng[1], size)); if (warned) { @@ -3623,28 +3775,41 @@ maybe_warn_for_bound (int opt, location_t loc, tree exp, tree func, return warned; } + bool maybe = pad && pad->dst.phi (); if (tree_int_cst_lt (maxobjsize, bndrng[0])) { if (bndrng[0] == bndrng[1]) warned = (func ? warning_at (loc, opt, - "%K%qD specified size %E " - "exceeds maximum object size %E", + (maybe + ? G_("%K%qD specified size %E may " + "exceed maximum object size %E") + : G_("%K%qD specified size %E " + "exceeds maximum object size %E")), exp, func, bndrng[0], maxobjsize) : warning_at (loc, opt, - "%Kspecified size %E " - "exceeds maximum object size %E", + (maybe + ? G_("%Kspecified size %E may exceed " + "maximum object size %E") + : G_("%Kspecified size %E exceeds " + "maximum object size %E")), exp, bndrng[0], maxobjsize)); else warned = (func ? warning_at (loc, opt, - "%K%qD specified size between %E and %E " - "exceeds maximum object size %E", + (maybe + ? G_("%K%qD specified size between %E and %E " + "may exceed maximum object size %E") + : G_("%K%qD specified size between %E and %E " + "exceeds maximum object size %E")), exp, func, bndrng[0], bndrng[1], maxobjsize) : warning_at (loc, opt, - "%Kspecified size between %E and %E " - "exceeds maximum object size %E", + (maybe + ? G_("%Kspecified size between %E and %E " + "may exceed maximum object size %E") + : G_("%Kspecified size between %E and %E " + "exceeds maximum object size %E")), exp, bndrng[0], bndrng[1], maxobjsize)); } else if (!size || tree_int_cst_le (bndrng[0], size)) @@ -3652,22 +3817,34 @@ maybe_warn_for_bound (int opt, location_t loc, tree exp, tree func, else if (tree_int_cst_equal (bndrng[0], bndrng[1])) warned = (func ? warning_at (loc, OPT_Wstringop_overflow_, - "%K%qD specified bound %E exceeds " - "destination size %E", + (maybe + ? G_("%K%qD specified bound %E may exceed " + "destination size %E") + : G_("%K%qD specified bound %E exceeds " + "destination size %E")), exp, func, bndrng[0], size) : warning_at (loc, OPT_Wstringop_overflow_, - "%Kspecified bound %E exceeds " - "destination size %E", + (maybe + ? G_("%Kspecified bound %E may exceed " + "destination size %E") + : G_("%Kspecified bound %E exceeds " + "destination size %E")), exp, bndrng[0], size)); else warned = (func ? warning_at (loc, OPT_Wstringop_overflow_, - "%K%qD specified bound [%E, %E] exceeds " - "destination size %E", + (maybe + ? G_("%K%qD specified bound [%E, %E] may exceed " + "destination size %E") + : G_("%K%qD specified bound [%E, %E] exceeds " + "destination size %E")), exp, func, bndrng[0], bndrng[1], size) : warning_at (loc, OPT_Wstringop_overflow_, - "%Kspecified bound [%E, %E] exceeds " - "destination size %E", + (maybe + ? G_("%Kspecified bound [%E, %E] exceeds " + "destination size %E") + : G_("%Kspecified bound [%E, %E] exceeds " + "destination size %E")), exp, bndrng[0], bndrng[1], size)); if (warned) @@ -3696,7 +3873,7 @@ maybe_warn_for_bound (int opt, location_t loc, tree exp, tree func, static bool warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2], - tree size, bool write, bool read) + tree size, bool write, bool read, bool maybe) { bool warned = false; @@ -3705,40 +3882,64 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2], if (tree_int_cst_equal (range[0], range[1])) warned = (func ? warning_n (loc, opt, tree_to_uhwi (range[0]), - "%K%qD accessing %E byte in a region " - "of size %E", - "%K%qD accessing %E bytes in a region " - "of size %E", + (maybe + ? G_("%K%qD may access %E byte in a region " + "of size %E") + : G_("%K%qD accessing %E byte in a region " + "of size %E")), + (maybe + ? G_ ("%K%qD may access %E bytes in a region " + "of size %E") + : G_ ("%K%qD accessing %E bytes in a region " + "of size %E")), exp, func, range[0], size) : warning_n (loc, opt, tree_to_uhwi (range[0]), - "%Kaccessing %E byte in a region " - "of size %E", - "%Kaccessing %E bytes in a region " - "of size %E", + (maybe + ? G_("%Kmay access %E byte in a region " + "of size %E") + : G_("%Kaccessing %E byte in a region " + "of size %E")), + (maybe + ? G_("%Kmay access %E bytes in a region " + "of size %E") + : G_("%Kaccessing %E bytes in a region " + "of size %E")), exp, range[0], size)); else if (tree_int_cst_sign_bit (range[1])) { /* Avoid printing the upper bound if it's invalid. */ warned = (func ? warning_at (loc, opt, - "%K%qD accessing %E or more bytes in " - "a region of size %E", + (maybe + ? G_("%K%qD may access %E or more bytes " + "in a region of size %E") + : G_("%K%qD accessing %E or more bytes " + "in a region of size %E")), exp, func, range[0], size) : warning_at (loc, opt, - "%Kaccessing %E or more bytes in " - "a region of size %E", + (maybe + ? G_("%Kmay access %E or more bytes " + "in a region of size %E") + : G_("%Kaccessing %E or more bytes " + "in a region of size %E")), exp, range[0], size)); } else warned = (func ? warning_at (loc, opt, - "%K%qD accessing between %E and %E bytes " - "in a region of size %E", + (maybe + ? G_("%K%qD may access between %E and %E " + "bytes in a region of size %E") + : G_("%K%qD accessing between %E and %E " + "bytes in a region of size %E")), exp, func, range[0], range[1], size) : warning_at (loc, opt, - "%Kaccessing between %E and %E bytes " - "in a region of size %E", + (maybe + ? G_("%Kmay access between %E and %E bytes " + "in a region of size %E") + : G_("%Kaccessing between %E and %E bytes " + "in a region of size %E")), exp, range[0], range[1], size)); return warned; @@ -3749,44 +3950,69 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2], if (tree_int_cst_equal (range[0], range[1])) warned = (func ? warning_n (loc, opt, tree_to_uhwi (range[0]), - "%K%qD writing %E byte into a region " - "of size %E overflows the destination", - "%K%qD writing %E bytes into a region " - "of size %E overflows the destination", + (maybe + ? G_("%K%qD may write %E byte into a region " + "of size %E") + : G_("%K%qD writing %E byte into a region " + "of size %E overflows the destination")), + (maybe + ? G_("%K%qD may write %E bytes into a region " + "of size %E") + : G_("%K%qD writing %E bytes into a region " + "of size %E overflows the destination")), exp, func, range[0], size) : warning_n (loc, opt, tree_to_uhwi (range[0]), - "%Kwriting %E byte into a region " - "of size %E overflows the destination", - "%Kwriting %E bytes into a region " - "of size %E overflows the destination", + (maybe + ? G_("%Kmay write %E byte into a region " + "of size %E") + : G_("%Kwriting %E byte into a region " + "of size %E overflows the destination")), + (maybe + ? G_("%Kmay write %E bytes into a region " + "of size %E") + : G_("%Kwriting %E bytes into a region " + "of size %E overflows the destination")), exp, range[0], size)); else if (tree_int_cst_sign_bit (range[1])) { /* Avoid printing the upper bound if it's invalid. */ warned = (func ? warning_at (loc, opt, - "%K%qD writing %E or more bytes into " - "a region of size %E overflows " - "the destination", + (maybe + ? G_("%K%qD may write %E or more bytes " + "into a region of size %E " + "the destination") + : G_("%K%qD writing %E or more bytes " + "into a region of size %E overflows " + "the destination")), exp, func, range[0], size) : warning_at (loc, opt, - "%Kwriting %E or more bytes into " - "a region of size %E overflows " - "the destination", + (maybe + ? G_("%Kmay write %E or more bytes into " + "a region of size %E") + : G_("%Kwriting %E or more bytes into " + "a region of size %E overflows " + "the destination")), exp, range[0], size)); } else warned = (func ? warning_at (loc, opt, - "%K%qD writing between %E and %E bytes " - "into a region of size %E overflows " - "the destination", + (maybe + ? G_("%K%qD may write between %E and %E bytes " + "into a region of size %E") + : G_("%K%qD writing between %E and %E bytes " + "into a region of size %E overflows " + "the destination")), exp, func, range[0], range[1], size) : warning_at (loc, opt, - "%Kwriting between %E and %E bytes " - "into a region of size %E overflows " - "the destination", + (maybe + ? G_("%Kmay write between %E and %E bytes " + "into a region of size %E") + : G_("%Kwriting between %E and %E bytes " + "into a region of size %E overflows " + "the destination")), exp, range[0], range[1], size)); return warned; @@ -3798,35 +4024,64 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2], warned = (func ? warning_n (loc, OPT_Wstringop_overread, tree_to_uhwi (range[0]), - "%K%qD reading %E byte from a region of size %E", - "%K%qD reading %E bytes from a region of size %E", exp, func, range[0], size) + (maybe + ? G_("%K%qD may reade %E byte from a region " + "of size %E") + : G_("%K%qD reading %E byte from a region " + "of size %E")), + (maybe + ? G_("%K%qD may read %E bytes from a region " + "of size %E") + : G_("%K%qD reading %E bytes from a region " + "of size %E")), + exp, func, range[0], size) : warning_n (loc, OPT_Wstringop_overread, tree_to_uhwi (range[0]), - "%Kreading %E byte from a region of size %E", - "%Kreading %E bytes from a region of size %E", + (maybe + ? G_("%Kmay read %E byte from a region " + "of size %E") + : G_("%Kreading %E byte from a region " + "of size %E")), + (maybe + ? G_("%Kmay read %E bytes from a region " + "of size %E") + : G_("%Kreading %E bytes from a region " + "of size %E")), exp, range[0], size)); else if (tree_int_cst_sign_bit (range[1])) { /* Avoid printing the upper bound if it's invalid. */ warned = (func ? warning_at (loc, OPT_Wstringop_overread, - "%K%qD reading %E or more bytes from " - "a region of size %E", + (maybe + ? G_("%K%qD may read %E or more bytes " + "from a region of size %E") + : G_("%K%qD reading %E or more bytes " + "from a region of size %E")), exp, func, range[0], size) : warning_at (loc, OPT_Wstringop_overread, - "%Kreading %E or more bytes from a region " - "of size %E", + (maybe + ? G_("%Kmay read %E or more bytes " + "from a region of size %E") + : G_("%Kreading %E or more bytes " + "from a region of size %E")), exp, range[0], size)); } else warned = (func ? warning_at (loc, OPT_Wstringop_overread, - "%K%qD reading between %E and %E bytes from " - "a region of size %E", + (maybe + ? G_("%K%qD may read between %E and %E bytes " + "from a region of size %E") + : G_("%K%qD reading between %E and %E bytes " + "from a region of size %E")), exp, func, range[0], range[1], size) : warning_at (loc, opt, - "%K reading between %E and %E bytes from " - "a region of size %E", + (maybe + ? G_("%Kmay read between %E and %E bytes " + "from a region of size %E") + : G_("%Kreading between %E and %E bytes " + "from a region of size %E")), exp, range[0], range[1], size)); if (warned) @@ -3878,28 +4133,57 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2], return warned; } -/* Issue an inform message describing the target of an access REF. +/* Issue one inform message describing each target of an access REF. WRITE is set for a write access and clear for a read access. */ -static void -inform_access (const access_ref &ref, access_mode mode) +void +access_ref::inform_access (access_mode mode) const { - if (!ref.ref) + const access_ref &aref = *this; + if (!aref.ref) return; + if (aref.phi ()) + { + /* Set MAXREF to refer to the largest object and fill ALL_REFS + with data for all objects referenced by the PHI arguments. */ + access_ref maxref; + auto_vec<access_ref> all_refs; + if (!get_ref (&all_refs, &maxref)) + return; + + /* Except for MAXREF, the rest of the arguments' offsets need not + reflect one added to the PHI itself. Determine the latter from + MAXREF on which the result is based. */ + const offset_int orng[] = + { + offrng[0] - maxref.offrng[0], + wi::smax (offrng[1] - maxref.offrng[1], offrng[0]), + }; + + /* Add the final PHI's offset to that of each of the arguments + and recurse to issue an inform message for it. */ + for (unsigned i = 0; i != all_refs.length (); ++i) + { + all_refs[i].add_offset (orng[0], orng[1]); + all_refs[i].inform_access (mode); + } + return; + } + /* 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 (); + if (wi::fits_shwi_p (aref.offrng[0])) + minoff = aref.offrng[0].to_shwi (); else - minoff = ref.offrng[0] < 0 ? diff_min : diff_max; + minoff = aref.offrng[0] < 0 ? diff_min : diff_max; - if (wi::fits_shwi_p (ref.offrng[1])) - maxoff = ref.offrng[1].to_shwi (); + if (wi::fits_shwi_p (aref.offrng[1])) + maxoff = aref.offrng[1].to_shwi (); if (maxoff <= diff_min || maxoff >= diff_max) /* Avoid mentioning an upper bound that's equal to or in excess @@ -3909,110 +4193,127 @@ inform_access (const access_ref &ref, access_mode mode) /* Convert size range and always include it since all sizes are meaningful. */ unsigned long long minsize = 0, maxsize = 0; - if (wi::fits_shwi_p (ref.sizrng[0]) - && wi::fits_shwi_p (ref.sizrng[1])) + if (wi::fits_shwi_p (aref.sizrng[0]) + && wi::fits_shwi_p (aref.sizrng[1])) { - minsize = ref.sizrng[0].to_shwi (); - maxsize = ref.sizrng[1].to_shwi (); + minsize = aref.sizrng[0].to_shwi (); + maxsize = aref.sizrng[1].to_shwi (); } + /* SIZRNG doesn't necessarily have the same range as the allocation + size determined by gimple_call_alloc_size (). */ char sizestr[80]; - location_t loc; - tree allocfn = NULL_TREE; - if (TREE_CODE (ref.ref) == SSA_NAME) - { - gimple *stmt = SSA_NAME_DEF_STMT (ref.ref); - gcc_assert (is_gimple_call (stmt)); - loc = gimple_location (stmt); - allocfn = gimple_call_fndecl (stmt); - if (!allocfn) - /* Handle calls through pointers to functions. */ - allocfn = gimple_call_fn (stmt); - - /* SIZRNG doesn't necessarily have the same range as the allocation - size determined by gimple_call_alloc_size (). */ + if (minsize == maxsize) + sprintf (sizestr, "%llu", minsize); + else + sprintf (sizestr, "[%llu, %llu]", minsize, maxsize); + + char offstr[80]; + if (minoff == 0 + && (maxoff == 0 || aref.sizrng[1] <= maxoff)) + offstr[0] = '\0'; + else if (minoff == maxoff) + sprintf (offstr, "%lli", (long long) minoff); + else + sprintf (offstr, "[%lli, %lli]", (long long) minoff, (long long) maxoff); - if (minsize == maxsize) - sprintf (sizestr, "%llu", minsize); - else - sprintf (sizestr, "[%llu, %llu]", minsize, maxsize); + location_t loc = UNKNOWN_LOCATION; + tree ref = this->ref; + tree allocfn = NULL_TREE; + if (TREE_CODE (ref) == SSA_NAME) + { + gimple *stmt = SSA_NAME_DEF_STMT (ref); + if (is_gimple_call (stmt)) + { + loc = gimple_location (stmt); + if (gimple_call_builtin_p (stmt, BUILT_IN_ALLOCA_WITH_ALIGN)) + { + /* Strip the SSA_NAME suffix from the variable name and + recreate an identifier with the VLA's original name. */ + ref = gimple_call_lhs (stmt); + ref = SSA_NAME_IDENTIFIER (ref); + const char *id = IDENTIFIER_POINTER (ref); + size_t len = strcspn (id, ".$"); + if (!len) + len = strlen (id); + ref = get_identifier_with_length (id, len); + } + else + { + /* Except for VLAs, retrieve the allocation function. */ + allocfn = gimple_call_fndecl (stmt); + if (!allocfn) + allocfn = gimple_call_fn (stmt); + if (TREE_CODE (allocfn) == SSA_NAME) + { + /* For an ALLOC_CALL via a function pointer make a small + effort to determine the destination of the pointer. */ + gimple *def = SSA_NAME_DEF_STMT (allocfn); + if (gimple_assign_single_p (def)) + { + tree rhs = gimple_assign_rhs1 (def); + if (DECL_P (rhs)) + allocfn = rhs; + else if (TREE_CODE (rhs) == COMPONENT_REF) + allocfn = TREE_OPERAND (rhs, 1); + } + } + } + } + else if (gimple_nop_p (stmt)) + /* Handle DECL_PARM below. */ + ref = SSA_NAME_VAR (ref); } - 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 + + if (DECL_P (ref)) + loc = DECL_SOURCE_LOCATION (ref); + else if (EXPR_P (ref) && EXPR_HAS_LOCATION (ref)) + loc = EXPR_LOCATION (ref); + else if (TREE_CODE (ref) != IDENTIFIER_NODE + && TREE_CODE (ref) != SSA_NAME) return; if (mode == access_read_write || mode == access_write_only) { if (allocfn == NULL_TREE) { - if (minoff == maxoff) - { - if (minoff == 0) - inform (loc, "destination object %qE", ref.ref); - else - inform (loc, "at offset %wi into destination object %qE", - minoff, ref.ref); - } + if (*offstr) + inform (loc, "at offset %s into destination object %qE of size %s", + offstr, ref, sizestr); else - inform (loc, "at offset [%wi, %wi] into destination object %qE", - minoff, maxoff, ref.ref); + inform (loc, "destination object %qE of size %s", ref, sizestr); return; } - if (minoff == maxoff) - { - if (minoff == 0) - inform (loc, "destination object of size %s allocated by %qE", - sizestr, allocfn); - else - inform (loc, - "at offset %wi into destination object of size %s " - "allocated by %qE", minoff, sizestr, allocfn); - } - else + if (*offstr) inform (loc, - "at offset [%wi, %wi] into destination object of size %s " - "allocated by %qE", - minoff, maxoff, sizestr, allocfn); - + "at offset %s into destination object of size %s " + "allocated by %qE", offstr, sizestr, allocfn); + else + inform (loc, "destination object of size %s allocated by %qE", + sizestr, allocfn); return; } - if (DECL_P (ref.ref)) + if (DECL_P (ref)) { - if (minoff == maxoff) - { - if (minoff == 0) - inform (loc, "source object %qD", ref.ref); - else - inform (loc, "at offset %wi into source object %qD", - minoff, ref.ref); - } + if (*offstr) + inform (loc, "at offset %s into source object %qD of size %s", + offstr, ref, sizestr); else - inform (loc, "at offset [%wi, %wi] into source object %qD", - minoff, maxoff, ref.ref); + inform (loc, "source object %qD of size %s", ref, sizestr); + return; } - if (minoff == maxoff) - { - if (minoff == 0) - inform (loc, "source object of size %s allocated by %qE", - sizestr, allocfn); - else - inform (loc, - "at offset %wi into source object of size %s " - "allocated by %qE", minoff, sizestr, allocfn); - } - else + if (*offstr) inform (loc, - "at offset [%wi, %wi] into source object of size %s " - "allocated by %qE", - minoff, maxoff, sizestr, allocfn); + "at offset %s into source object of size %s allocated by %qE", + offstr, sizestr, allocfn); + else + inform (loc, "source object of size %s allocated by %qE", + sizestr, allocfn); } /* Helper to set RANGE to the range of BOUND if it's nonnull, bounded @@ -4232,17 +4533,18 @@ check_access (tree exp, tree dstwrite, = mode == access_read_only || mode == access_read_write; const bool write = mode == access_write_only || mode == access_read_write; + const bool maybe = pad && pad->dst.parmarray; warned = warn_for_access (loc, func, exp, OPT_Wstringop_overflow_, range, dstsize, - write, read && !builtin); + write, read && !builtin, maybe); } if (warned) { TREE_NO_WARNING (exp) = true; if (pad) - inform_access (pad->dst, pad->mode); + pad->dst.inform_access (pad->mode); } /* Return error when an overflow has been detected. */ @@ -4325,12 +4627,13 @@ check_access (tree exp, tree dstwrite, const bool read = mode == access_read_only || mode == access_read_write; + const bool maybe = pad && pad->dst.parmarray; if (warn_for_access (loc, func, exp, OPT_Wstringop_overread, range, - slen, false, read)) + slen, false, read, maybe)) { TREE_NO_WARNING (exp) = true; if (pad) - inform_access (pad->src, access_read_only); + pad->src.inform_access (access_read_only); } return false; } @@ -4462,11 +4765,12 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, /* For an access to an object referenced to by the function parameter PTR of pointer type, and set RNG[] to the range of sizes of the object obtainedfrom the attribute access specification for the current function. + Set STATIC_ARRAY if the array parameter has been declared [static]. Return the function parameter on success and null otherwise. */ tree gimple_parm_array_size (tree ptr, wide_int rng[2], - range_query * /* = NULL */) + bool *static_array /* = NULL */) { /* For a function argument try to determine the byte size of the array from the current function declaratation (e.g., attribute access or @@ -4498,6 +4802,9 @@ gimple_parm_array_size (tree ptr, wide_int rng[2], if (warn_array_parameter < 2 && !access->static_p) return NULL_TREE; + if (static_array) + *static_array = access->static_p; + rng[0] = wi::zero (prec); rng[1] = wi::uhwi (access->minsize, prec); /* Multiply the array bound encoded in the attribute by the size @@ -4645,6 +4952,84 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], return NULL_TREE; } +/* A helper of compute_objsize() to determine the size from an assignment + statement STMT with the RHS of either MIN_EXPR or MAX_EXPR. */ + +static bool +handle_min_max_size (gimple *stmt, int ostype, access_ref *pref, + bitmap *visited, range_query *rvals) +{ + tree_code code = gimple_assign_rhs_code (stmt); + + tree ptr = gimple_assign_rhs1 (stmt); + + /* In a valid MAX_/MIN_EXPR both operands must refer to the same array. + Determine the size/offset of each and use the one with more or less + space remaining, respectively. If either fails, use the information + determined from the other instead, adjusted up or down as appropriate + for the expression. */ + access_ref aref[2] = { *pref, *pref }; + if (!compute_objsize (ptr, ostype, &aref[0], visited, rvals)) + { + aref[0].base0 = false; + aref[0].offrng[0] = aref[0].offrng[1] = 0; + aref[0].add_max_offset (); + aref[0].set_max_size_range (); + } + + ptr = gimple_assign_rhs2 (stmt); + if (!compute_objsize (ptr, ostype, &aref[1], visited, rvals)) + { + aref[1].base0 = false; + aref[1].offrng[0] = aref[1].offrng[1] = 0; + aref[1].add_max_offset (); + aref[1].set_max_size_range (); + } + + if (!aref[0].ref && !aref[1].ref) + /* Fail if the identity of neither argument could be determined. */ + return false; + + bool i0 = false; + if (aref[0].ref && aref[0].base0) + { + if (aref[1].ref && aref[1].base0) + { + /* If the object referenced by both arguments has been determined + set *PREF to the one with more or less space remainng, whichever + is appopriate for CODE. + TODO: Indicate when the objects are distinct so it can be + diagnosed. */ + i0 = code == MAX_EXPR; + const bool i1 = !i0; + + if (aref[i0].size_remaining () < aref[i1].size_remaining ()) + *pref = aref[i1]; + else + *pref = aref[i0]; + return true; + } + + /* If only the object referenced by one of the arguments could be + determined, use it and... */ + *pref = aref[0]; + i0 = true; + } + else + *pref = aref[1]; + + const bool i1 = !i0; + /* ...see if the offset obtained from the other pointer can be used + to tighten up the bound on the offset obtained from the first. */ + if ((code == MAX_EXPR && aref[i1].offrng[1] < aref[i0].offrng[0]) + || (code == MIN_EXPR && aref[i0].offrng[0] < aref[i1].offrng[1])) + { + pref->offrng[0] = aref[i0].offrng[0]; + pref->offrng[1] = aref[i0].offrng[1]; + } + return true; +} + /* 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). @@ -4709,6 +5094,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, if (code == COMPONENT_REF) { tree ref = TREE_OPERAND (ptr, 0); + if (TREE_CODE (TREE_TYPE (ref)) == UNION_TYPE) + /* In accesses through union types consider the entire unions + rather than just their members. */ + ostype = 0; tree field = TREE_OPERAND (ptr, 1); if (ostype == 0) @@ -4963,8 +5352,10 @@ 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]; - if (tree ref = gimple_parm_array_size (ptr, wr, rvals)) + bool static_array = false; + if (tree ref = gimple_parm_array_size (ptr, wr, &static_array)) { + pref->parmarray = !static_array; pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); pref->ref = ref; @@ -4974,14 +5365,19 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, 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 (gimple_code (stmt) == GIMPLE_PHI) + { + pref->ref = ptr; + access_ref phi_ref = *pref; + if (!pref->get_ref (NULL, &phi_ref, ostype, visited, rvals)) + return false; + *pref = phi_ref; + pref->ref = ptr; + return true; + } if (!is_gimple_assign (stmt)) { @@ -4991,16 +5387,16 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited, 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 (code == MAX_EXPR || code == MIN_EXPR) + return handle_min_max_size (stmt, ostype, pref, visited, rvals); + + ptr = gimple_assign_rhs1 (stmt); + if (code == POINTER_PLUS_EXPR && TREE_CODE (TREE_TYPE (ptr)) == POINTER_TYPE) { diff --git a/gcc/builtins.h b/gcc/builtins.h index c09f36da02b..4a2e34351be 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -153,6 +153,8 @@ extern void warn_string_no_nul (location_t, tree, const char *, tree, extern tree unterminated_array (tree, tree * = NULL, bool * = NULL); extern bool builtin_with_linkage_p (tree); +class range_query; + /* Describes a reference to an object used in an access. */ struct access_ref { @@ -162,17 +164,12 @@ struct access_ref is a constant zero. */ access_ref (tree = NULL_TREE, bool = false); - /* Reference to the accessed object(s). */ - tree ref; + /* Return the PHI node REF refers to or null if it doesn't. */ + gphi *phi () const; - /* Range of byte offsets into and sizes of the object(s). */ - offset_int offrng[2]; - offset_int sizrng[2]; - /* Range of the bound of the access: denotes that the access - is at least BNDRNG[0] bytes but no more than BNDRNG[1]. - For string functions the size of the actual access is - further constrained by the length of the string. */ - offset_int bndrng[2]; + /* Return the object to which REF refers. */ + tree get_ref (vec<access_ref> *, access_ref * = NULL, int = 1, + bitmap * = NULL, range_query * = NULL) const; /* Return true if OFFRNG is the constant zero. */ bool offset_zero () const @@ -211,6 +208,22 @@ struct access_ref add_offset (-maxoff - 1, maxoff); } + /* Issue an informational message describing the target of an access + with the given mode. */ + void inform_access (access_mode) const; + + /* Reference to the accessed object(s). */ + tree ref; + + /* Range of byte offsets into and sizes of the object(s). */ + offset_int offrng[2]; + offset_int sizrng[2]; + /* Range of the bound of the access: denotes that the access + is at least BNDRNG[0] bytes but no more than BNDRNG[1]. + For string functions the size of the actual access is + further constrained by the length of the string. */ + offset_int bndrng[2]; + /* Used to fold integer expressions when called from front ends. */ tree (*eval)(tree); /* Set if trailing one-element arrays should be treated as flexible @@ -219,6 +232,9 @@ struct access_ref /* Set if valid offsets must start at zero (for declared and allocated objects but not for others referenced by pointers). */ bool base0; + /* Set if REF refers to a function array parameter not declared + static. */ + bool parmarray; }; /* Describes a pair of references used in an access by built-in @@ -242,10 +258,9 @@ struct access_data access_mode mode; }; -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 gimple_parm_array_size (tree, wide_int[2], bool * = 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); diff --git a/gcc/testsuite/c-c++-common/Wstringop-overflow-2.c b/gcc/testsuite/c-c++-common/Wstringop-overflow-2.c index 7c7932e3cf0..676643168cc 100644 --- a/gcc/testsuite/c-c++-common/Wstringop-overflow-2.c +++ b/gcc/testsuite/c-c++-common/Wstringop-overflow-2.c @@ -10,7 +10,7 @@ void sink (void*); struct Ax { char n; - char a[]; // { dg-message "declared here" } + char a[]; // { dg-message "destination object" "note" } }; // Verify warning for a definition with no initializer. @@ -91,7 +91,7 @@ void gaxx (void) struct A0 { char n; - char a[0]; // { dg-message "declared here" } + char a[0]; // { dg-message "destination object" "note" } }; // Verify warning for a definition with no initializer. @@ -158,7 +158,7 @@ void ga0x (void) struct A1 { char n; - char a[1]; // { dg-message "declared here" } + char a[1]; // { dg-message "destination object" "note" } }; // Verify warning for a definition with no initializer. @@ -256,7 +256,7 @@ void ga1x (void) struct A1i { char n; - char a[1]; // { dg-message "declared here" } + char a[1]; // { dg-message "destination object" } char x; }; diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-11.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-11.c index f5dac458d1e..ec3c97e8102 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-11.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-11.c @@ -72,7 +72,7 @@ void test_memset_array_range_cst_off (void) { T (SR (-7, 7), 1, 7); T (SR (-1, 1), 1, 7); - T (SR (-1, 1), 1, 9); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "pr89428" { xfail *-*-*} } */ + T (SR (-1, 1), 1, 9); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "pr89428" } */ T (SR ( 1, 2), 1, 1); T (SR ( 1, 2), 1, 5); @@ -147,7 +147,7 @@ void test_memcpy_array_range_cst_off (const void *s) { T (SR (-7, 7), 1, 7); T (SR (-1, 1), 1, 7); - T (SR (-1, 1), 1, 9); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "pr89428" { xfail *-*-*} } */ + T (SR (-1, 1), 1, 9); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "pr89428" } */ T (SR ( 1, 2), 1, 1); T (SR ( 1, 2), 1, 5); @@ -224,7 +224,7 @@ void test_strcpy_array_range_cst_off (const char *s) { T (SR (-7, 7), 1, 6); T (SR (-1, 1), 1, 6); - T (SR (-1, 1), 1, 8); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "pr89428" { xfail *-*-*} } */ + T (SR (-1, 1), 1, 8); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "pr89428" } */ T (SR ( 1, 2), 1, 0); T (SR ( 1, 2), 1, 1); T (SR ( 1, 2), 1, 4); @@ -290,7 +290,7 @@ void test_strncpy_array_range_cst_off (const char *s) { T (SR (-7, 7), 1, 7); T (SR (-1, 1), 1, 7); - T (SR (-1, 1), 1, 9); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "pr89428" { xfail *-*-*} } */ + T (SR (-1, 1), 1, 9); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "pr89428" } */ T (SR ( 1, 2), 1, 1); T (SR ( 1, 2), 1, 5); diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-12.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-12.c index 1e67b5fd928..7c3dc8c0544 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-12.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-12.c @@ -25,7 +25,9 @@ void test_memcpy_array_cst_range_off (const void *s) T (d + UR (1, 2), 5); T (d + UR (0, 1), 6); - T (d + UR (0, 1), 7); /* { dg-warning ".memcpy. writing 6 bytes into a region of size 5 overflows the destination" "pr89428" { xfail *-*-* } } */ + /* The warning below should be "writing" but the [0, 1] range + is somehow lost and get_range_info() returns VR_VARYING. */ + T (d + UR (0, 1), 7); /* { dg-warning ".memcpy. writing 7 bytes into a region of size 6 overflows the destination" "pr89428" { xfail *-*-* } } */ T (d + UR (1, 2), 6); /* { dg-warning ".memcpy. writing 6 bytes into a region of size 5 overflows the destination" } */ T (d + UR (1, 2), 7); /* { dg-warning "writing 7 bytes into a region of size 5 " } */ @@ -48,7 +50,8 @@ void test_memcpy_array_range_range_off (const void *s) char *d = ga7 + UR (0, 1); T (d + SR (-1, 0), 1); T (d + SR (-1, 0), 7); - T (d + SR (-1, 0), 9); /* { dg-warning "writing 1 byte into a region of size 0 " "pr89350" { xfail *-*-* } } */ + T (d + SR (-1, 0), 8); /* { dg-warning "writing 8 bytes into a region of size 7 " } */ + T (d + SR (-1, 0), 9); /* { dg-warning "writing 9 bytes into a region of size 7 " "pr89350" } */ } diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-17.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-17.c index fb81420c933..9c05d04f90c 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-17.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-17.c @@ -13,7 +13,7 @@ void sink (void*); void call_copy_n (const char *s) { - char a[7]; // { dg-message "declared here" } + char a[7]; // { dg-message "at offset 7 into destination object 'a'" } copy_n (a, "1234567", 7); sink (a); } diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c index 37c1ca29713..607c27989a3 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c @@ -261,8 +261,7 @@ void test_strcpy_warn (const char *s) that the conversion from signed int to size_t doesn't prevent the detection. */ int n = strlen (a); - char *t = (char*)calloc (n, 1); // { dg-message "at offset 0 to an object with size 3 allocated by 'calloc' here" "calloc note 1" { xfail *-*-* } } - // { dg-message "at offset 0 to an object with size at most 3 allocated by 'calloc' here" "calloc note 2" { target *-*-* } .-1 } + char *t = (char*)calloc (n, 1); // { dg-message "destination object of size 3 allocated by 'calloc'" "note" } strcpy (t, a); // { dg-warning "writing 4 bytes into a region of size (between 0 and )?3 " } sink (t); @@ -271,8 +270,7 @@ void test_strcpy_warn (const char *s) { const char a[] = "1234"; size_t n = strlen (a); - char *t = (char*)malloc (n); // { dg-message "at offset 0 to an object with size 4 allocated by 'malloc' here" "malloc note 1" { xfail *-*-* } } - // { dg-message "at offset 0 to an object with size at most 4 allocated by 'malloc' here" "malloc note 2" { target *-*-* } .-1 } + char *t = (char*)malloc (n); // { dg-message "destination object of size 4 allocated by 'malloc'" "note" } strcpy (t, a); // { dg-warning "writing 5 bytes into a region of size (between 0 and )?4 " } sink (t); } @@ -280,14 +278,14 @@ void test_strcpy_warn (const char *s) // Exercise PR middle-end/85484. { size_t len = strlen (s); - char vla[len]; // { dg-message "at offset 0 to an object declared here" "vla note" } + char vla[len]; // { dg-message "destination object 'vla'" "vla note" } strcpy (vla, s); // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" } sink (vla); } { size_t n = strlen (s); - char *t = (char*)malloc (n); // { dg-message "at offset 0 to an object allocated by 'malloc' here" "malloc note" } + char *t = (char*)malloc (n); // { dg-message "allocated by 'malloc'" "malloc note" } strcpy (t, s); // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" } sink (t); } diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c index be7f51ad3a5..5009fb5763a 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c @@ -40,27 +40,27 @@ void same_size_and_offset_idx_cst (void) const size_t n = UR (2, 3); T (n, n, -4); // { dg-warning "writing 1 byte into a region of size 0" } - // { dg-message "at offset \\\[-2, -1] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[-2, -1] into destination object of size \\\[2, 3] allocated by 'alloc1'" "note" { target *-*-* } .-1 } T (n, n, -3); T (n, n, -2); T (n, n, -1); T (n, n, 0); T (n, n, 1); // { dg-warning "writing 1 byte into a region of size 0" } - // { dg-message "at offset \\\[3, 4] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + // { dg-message "at offset 3 into destination object of size \\\[2, 3] allocated by 'alloc1'" "note" { target *-*-* } .-1 } } { const size_t n = UR (3, 4); T (n, n, -5); // { dg-warning "writing 1 byte into a region of size 0" } - // { dg-message "at offset \\\[-2, -1] to an object with size between 3 and 4 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[-2, -1] into destination object of size \\\[3, 4] allocated by 'alloc1'" "note" { target *-*-* } .-1 } T (n, n, -4); T (n, n, -3); T (n, n, -2); T (n, n, -1); T (n, n, 0); T (n, n, 1); // { dg-warning "writing 1 byte into a region of size 0" } - // { dg-message "at offset \\\[4, 5] to an object with size between 3 and 4 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + // { dg-message "at offset 4 into destination object of size \\\[3, 4] allocated by 'alloc1'" "note" { target *-*-* } .-1 } } { @@ -84,15 +84,15 @@ void different_size_and_offset_idx_cst (void) const size_t i = UR (1, 2); T (n, i, -4); // { dg-warning "writing 1 byte into a region of size 0" } - // { dg-message "at offset \\\[-3, -2] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[-3, -2] into destination object of size \\\[2, 3] allocated by 'alloc1'" "note" { target *-*-* } .-1 } T (n, i, -3); // { dg-warning "writing 1 byte into a region of size 0" } - // { dg-message "at offset \\\[-2, -1] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[-2, -1] into destination object of size \\\[2, 3] allocated by 'alloc1'" "note" { target *-*-* } .-1 } T (n, i, -2); T (n, i, -1); T (n, i, 0); T (n, i, 1); T (n, i, 2); // { dg-warning "writing 1 byte into a region of size 0" } - // { dg-message "at offset \\\[3, 4] to an object with size between 2 and 3 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + // { dg-message "at offset 3 into destination object of size \\\[2, 3] allocated by 'alloc1'" "note" { target *-*-* } .-1 } } { @@ -100,20 +100,20 @@ void different_size_and_offset_idx_cst (void) const size_t i = UR (2, 5); T (n, i, -6); // { dg-warning "writing 1 byte into a region of size 0" } - // { dg-message "at offset \\\[-4, -1] to an object with size between 3 and 4 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[-4, -2] into destination object of size \\\[3, 4] allocated by 'alloc1'" "note" { target *-*-* } .-1 } - /* The offsets -5 and -4 are both necessarily invalid even if the sum - (i - 5) and (i - 4) are (or could be) in bounds because they imply - that the intermediate offset (p + i) is out of bounds. */ - T (n, i, -5); // { dg-warning "" "intermediate offset" { xfail *-*-* } } - T (n, i, -4); // { dg-warning "" "intermediate offset" { xfail *-*-* } } + /* The offset -5 is necessarily invalid even if the sum (i - 5) is (or + could be) in bounds because it implies that the intermediate offset + (p + i) is out of bounds. */ + T (n, i, -5); // { dg-warning "writing 1 byte into a region of size 0 " } + T (n, i, -4); T (n, i, -3); T (n, i, -2); T (n, i, -1); T (n, i, 0); T (n, i, 1); T (n, i, 2); // { dg-warning "writing 1 byte into a region of size 0" } - // { dg-message "at offset \\\[4, 7] to an object with size between 3 and 4 allocated by 'alloc1'" "note" { target *-*-* } .-1 } + // { dg-message "at offset 4 into destination object of size \\\[3, 4] allocated by 'alloc1'" "note" { target *-*-* } .-1 } } } @@ -133,11 +133,8 @@ void different_size_and_offset_idx_var (void) T (n, i, SR ( 0, 1)); T (n, i, SR ( 1, 2)); T (n, i, SR ( 2, 3)); - /* The warning is issued below but the offset and the size in - the note are wrong. See the FIXME in compute_objsize(). */ T (n, i, SR ( 3, 4)); // { dg-warning "\\\[-Wstringop-overflow" } - // { dg-message "at offset 4 to an object with size between 3 and 4 allocated by 'alloc1'" "pr92940 note: offset addition" { xfail *-*-* } .-1 } - // { dg-message "at offset . to an object with size . allocated by 'alloc1'" "note: offset addition" { target *-*-* } .-2 } + // { dg-message "at offset 4 into destination object of size \\\[3, 4] allocated by 'alloc1'" "pr92940 note: offset addition" { target *-*-* } .-1 } } } diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-29.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-29.c index c011d05e89f..f13abbd7ca0 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-29.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-29.c @@ -11,7 +11,7 @@ void sink (void*); void direct_call (void) { - char *q = allocfn (0); // { dg-message "at offset 0 to an object with size 0 allocated by 'allocfn'" } + char *q = allocfn (0); // { dg-message "object of size 0 allocated by 'allocfn'" "note" } q[0] = 0; // { dg-warning "\\\[-Wstringop-overflow" } sink (q); } @@ -20,7 +20,7 @@ void direct_call (void) void local_ptr_call (void) { allocfn_t *ptr = allocfn; - char *q = ptr (1); // { dg-message "at offset -1 to an object with size 1 allocated by 'allocfn'" } + char *q = ptr (1); // { dg-message "at offset -1 into destination object of size 1 allocated by 'allocfn'" "note" } q[0] = 0; q[-1] = 0; // { dg-warning "\\\[-Wstringop-overflow" } sink (q); @@ -32,7 +32,7 @@ void global_ptr_call (void) extern allocfn_t *ptralloc; allocfn_t *ptr = ptralloc; - char *q = ptr (2); // { dg-message "at offset 3 to an object with size 2 allocated by 'ptralloc'" } + char *q = ptr (2); // { dg-message "at offset 3 into destination object of size 2 allocated by 'ptralloc'" "note" } q[0] = 0; q[1] = 1; q[3] = 3; // { dg-warning "\\\[-Wstringop-overflow" } @@ -44,7 +44,7 @@ void global_ptr_array_call (void) extern allocfn_t * (arralloc[]); allocfn_t *ptr = arralloc[0]; - char *q = ptr (2); // { dg-message "at offset 3 to an object with size 2 allocated by 'ptr'" } + char *q = ptr (2); // { dg-message "at offset 3 into destination object of size 2 allocated by 'ptr'" "note" } q[0] = 1; q[1] = 2; q[3] = 3; // { dg-warning "\\\[-Wstringop-overflow" } @@ -56,7 +56,7 @@ struct S { allocfn_t *ptralloc; }; void member_ptr_call (struct S *p) { - char *q = p->ptralloc (3); // { dg-message "at offset 5 to an object with size 3 allocated by 'ptralloc' here" } + char *q = p->ptralloc (3); // { dg-message "at offset 5 into destination object of size 3 allocated by 'ptralloc'" "note" } q[0] = 0; q[1] = 1; q[2] = 2; diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c index 46f8fed79f3..d9cf32d8784 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c @@ -67,7 +67,7 @@ void* warn_malloc_3_5 (const char *s, unsigned n) { if (n < 3 || 5 < n) n = 3; - char *p = (char*)malloc (n); // { dg-message "at offset 1 into destination object of size \\\[3, 5] allocated by 'malloc'" } + char *p = (char*)malloc (n); // { dg-message "at offset 1 into destination object of size \\\[3, 5] allocated by 'malloc'" "note" } // The size below should be a range like the one above. strncpy (p + 1, s, 5); // { dg-warning "writing 5 bytes into a region of size 4 " } return p; @@ -89,7 +89,7 @@ void* warn_usr_alloc_3_5 (UsrAlloc *usr_alloc, const char *s, unsigned n) { if (n < 3 || 5 < n) n = 3; - char *p = (char*)usr_alloc (n, 3); // { dg-message "at offset 1 into destination object of size \\\[9, 15] allocated by 'usr_alloc'" } + char *p = (char*)usr_alloc (n, 3); // { dg-message "at offset 1 into destination object of size \\\[9, 15] allocated by 'usr_alloc'" "note" } // The size below should be a range like the one above. strncpy (p + 1, s, 15); // { dg-warning "writing 15 bytes into a region of size 14 " } return p; @@ -179,67 +179,67 @@ void test_note (const char *s) extern void sink (void*); { - char a[1][1][2]; // { dg-message "destination object" } + char a[1][1][2]; // { dg-message "destination object" "note" } strncpy (a[0][0], s, 3); // { dg-warning "writing 3 bytes into a region of size 2 " } sink (a); } { - char a[1][1][2]; // { dg-message "at offset 2 into " } + char a[1][1][2]; // { dg-message "at offset 2 into " "note" } strncpy (a[0][1], s, 3); // { dg-warning "writing 3 bytes into a region of size 0 " } sink (a); } { - char a[1][2][2]; // { dg-message "destination object" } + char a[1][2][2]; // { dg-message "destination object" "note" } strncpy (a[0][0], s, 3); // { dg-warning "writing 3 bytes into a region of size 2 " } sink (a); } { - char a[1][2][2]; // { dg-message "at offset 2 into " } + char a[1][2][2]; // { dg-message "at offset 2 into " "note" } strncpy (a[0][1], s, 3); // { dg-warning "writing 3 bytes into a region of size 2 " } sink (a); } { - char a[1][2][2]; // { dg-message "at offset 4 into " } + char a[1][2][2]; // { dg-message "at offset 4 into " "note" } strncpy (a[1][0], s, 3); // { dg-warning "writing 3 bytes into a region of size 0 " } sink (a); } { - char a[2][1][2]; // { dg-message "at offset 2 into " } + char a[2][1][2]; // { dg-message "at offset 2 into " "note" } strncpy (a[0][1], s, 3); // { dg-warning "writing 3 bytes into a region of size 0 " } sink (a); } { - char a[2][1][2]; // { dg-message "at offset 2 into " } + char a[2][1][2]; // { dg-message "at offset 2 into " "note" } strncpy (a[1][0], s, 3); // { dg-warning "writing 3 bytes into a region of size 2 " } sink (a); } { - char a[2][2][3]; // { dg-message "at offset 9 into " } + char a[2][2][3]; // { dg-message "at offset 9 into " "note" } strncpy (a[1][1], s, 4); // { dg-warning "writing 4 bytes into a region of size 3 " } sink (a); } { - char a[2][3][3]; // { dg-message "at offset 12 into " } + char a[2][3][3]; // { dg-message "at offset 12 into " "note" } strncpy (a[1][1], s, 5); // { dg-warning "writing 5 bytes into a region of size 3 " } sink (a); } { - char a[2][3][3]; // { dg-message "at offset 12 into " } + char a[2][3][3]; // { dg-message "at offset 12 into " "note" } strncpy (a[1][1], s, 6); // { dg-warning "writing 6 bytes into a region of size 3 " } sink (a); } { - char a[2][3][3]; // { dg-message "at offset 15 into " } + char a[2][3][3]; // { dg-message "at offset 15 into " "note" } strncpy (a[1][2], s, 7); // { dg-warning "writing 7 bytes into a region of size 3 " } sink (a); } diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c index a4d78b21cd1..b126fcbdcae 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c @@ -53,7 +53,7 @@ void nowarn_memchr_anti_range_memset_cst (const void *s, size_t n) 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" } + char *p = malloc (4); // { dg-message "destination object of size 4 " "note" } sink (p); p = memchr (p, '1', 4); @@ -62,7 +62,7 @@ void warn_memchr_cst_memset_cst (const void *s) 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" } + char *p = malloc (4); // { dg-message "destination object of size 4 " "note" } sink (p); p = memchr (p, '1', n); @@ -79,9 +79,9 @@ void warn_memchr_var_memset_range (const void *s, unsigned n) 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 } + // { dg-message ": destination object of size \\\[5, 7]" "note 1" { target *-*-* } .-1 } + // { dg-message "at offset \\\[1, 7] into destination object of size \\\[5, 7]" "note 2" { target *-*-* } .-2 } + // { dg-message "at offset \\\[2, 7] into destination object of size \\\[5, 7]" "note 3" { target *-*-* } .-3 } sink (p0); char *p1 = memchr (p0, '1', n); diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c index 02b14ee2eda..cb2c329aa84 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c @@ -26,7 +26,7 @@ void nowarn_c32 (char c) void warn_c32 (char c) { - extern char warn_a32[32]; // { dg-message "at offset 32 to object 'warn_a32' with size 32" } + extern char warn_a32[32]; // { dg-message "at offset 32 into destination object 'warn_a32' of size 32" "note" } void *p = warn_a32 + 1; *(C32*)p = (C32){ c }; // { dg-warning "writing 1 byte into a region of size 0" } diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-50.s b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.s new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-54.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-54.c index 26568f8366d..f5929c9e7d6 100644 --- a/gcc/testsuite/gcc.dg/Wstringop-overflow-54.c +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-54.c @@ -15,7 +15,7 @@ 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'" } + caxcc; // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'caxcc'" "note" } char *p = caxcc.a; size_t idx = DIFF_MAX - 4; @@ -38,7 +38,7 @@ void char_flexarray_cst_off_cst_size (void) void char_flexarray_var_off_cst_size (ptrdiff_t idx) { extern struct { char n, a[]; } - caxvc; // { dg-message "destination object 'caxvc'" } + caxvc; // { dg-message "destination object 'caxvc'" "note" } char *p = caxvc.a; @@ -55,7 +55,7 @@ void char_flexarray_var_off_cst_size (ptrdiff_t idx) void char_flexarray_var_off_var_size (size_t n, ptrdiff_t idx) { extern struct { char n, a[]; } - caxvv; // { dg-message "destination object 'caxvv'" } + caxvv; // { dg-message "destination object 'caxvv'" "note" } char *p = caxvv.a; @@ -76,7 +76,7 @@ void char_flexarray_var_off_var_size (size_t n, ptrdiff_t idx) 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" } + *p = __builtin_malloc (n); // { dg-message "at offset \\d+ into destination object" "note" } if (idx < DIFF_MAX - 4) idx = DIFF_MAX - 4; @@ -91,7 +91,7 @@ void alloc_array_var_off_cst_size (size_t n, ptrdiff_t idx) 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'" } + iaxc; // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'iaxc'" "note" } int *p = iaxc.a; size_t idx = DIFF_MAX / sizeof *p - 1; diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-58.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-58.c new file mode 100644 index 00000000000..b81186cfb94 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-58.c @@ -0,0 +1,260 @@ +/* PR middle-end/92936 - missing warning on a past-the-end store to a PHI + Exercise warnings for writing into one of two or more declared objects. + { dg-do compile } + { dg-options "-O2 -Wall -Wno-array-bounds -ftrack-macro-expansion=0" } */ + +#include "range.h" + +#define INT_MAX __INT_MAX__ + +extern void* memset (void*, int, size_t); +#define memset(d, c, n) sink (memset (d, c, n)) + +void sink (int, ...); +#define sink(...) sink (0, __VA_ARGS__) + +volatile int cond1, cond2; + +extern char ca0[0], ca1[1], ca2[2], ca3[3], ca4[4], + ca5[5], ca6[6], ca7[7], ca8[8], ca9[9], cax[]; + +#define CHOOSE_DECL_2(n1, n2) \ + (cond1 ? ca ## n1 : ca ## n2) +#define CHOOSE_DECL_3(n1, n2, n3) \ + (cond1 < 0 ? ca ## n1 : 0 < cond1 ? ca ## n2 : ca ## n3) + + +void memset_decl_2 (void) +{ + { + char *p0_1 = CHOOSE_DECL_2 (0, 1); + + memset (p0_1, 0, 0); + /* Writing more than the smallest destination should trigger a "may + write" warning if the access is unconditionally reachable from + the block where the pointer to either object is assigned. */ + memset (p0_1, 0, 1); + memset (p0_1, 0, 2); // { dg-warning "memset' writing 2 bytes into a region of size 1 " } + memset (p0_1, 0, 9); // { dg-warning "memset' writing 9 bytes into a region of size 1 " } + } + + { + char *p0_x = CHOOSE_DECL_2 (0, x); + + memset (p0_x, 0, 0); + memset (p0_x, 0, 1); + memset (p0_x, 0, 2); + memset (p0_x, 0, 9); + } + + { + char *p3_5 = CHOOSE_DECL_2 (3, 5); + + memset (p3_5, 0, 1); + memset (p3_5, 0, 3); + memset (p3_5, 0, 4); + memset (p3_5, 0, 5); + memset (p3_5, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + { + char *p5_3 = CHOOSE_DECL_2 (5, 3); + + memset (p5_3, 0, 3); + memset (p5_3, 0, 4); + memset (p5_3, 0, 5); + memset (p5_3, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + { + char *px_3 = CHOOSE_DECL_2 (x, 3); + + memset (px_3, 0, 1); + memset (px_3, 0, 3); + memset (px_3, 0, 4); + memset (px_3, 0, 1234); + } + + { + char *p5_x = CHOOSE_DECL_2 (5, x); + + memset (p5_x, 0, 1); + memset (p5_x, 0, 5); + memset (p5_x, 0, 6); + memset (p5_x, 0, 1234); + } + +} + + +void memset_decl_3 (void) +{ + { + char *p0_1_2 = CHOOSE_DECL_3 (0, 1, 2); + memset (p0_1_2, 0, 0); + memset (p0_1_2, 0, 1); + memset (p0_1_2, 0, 2); + memset (p0_1_2, 0, 3); // { dg-warning "memset' writing 3 bytes into a region of size 2 " } + memset (p0_1_2, 0, 9); // { dg-warning "memset' writing 9 bytes into a region of size 2 " } + } + + { + char *p0_2_x = CHOOSE_DECL_3 (0, 2, x); + + memset (p0_2_x, 0, 0); + memset (p0_2_x, 0, 1); + memset (p0_2_x, 0, 3); + memset (p0_2_x, 0, 9); + } + + { + char *p3_4_5 = CHOOSE_DECL_3 (3, 4, 5); + + memset (p3_4_5, 0, 3); + memset (p3_4_5, 0, 4); + memset (p3_4_5, 0, 5); + memset (p3_4_5, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + { + char *p5_3_4 = CHOOSE_DECL_3 (5, 3, 4); + + memset (p5_3_4, 0, 3); + memset (p5_3_4, 0, 4); + memset (p5_3_4, 0, 5); + memset (p5_3_4, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + { + char *p9_8_7 = CHOOSE_DECL_3 (9, 8, 7); + + memset (p9_8_7, 0, 7); + memset (p9_8_7, 0, 8); + memset (p9_8_7, 0, 9); + memset (p9_8_7, 0, 10); // { dg-warning "memset' writing 10 bytes into a region of size 9 " } + } +} + + +/* Verify conditionally writing into one of two objects with the same + size. */ + +void memset_decl_2_same_size (int i) +{ + { + char a4_1[4], a4_2[4]; + char *p4 = cond1 ? a4_1 : a4_2; + + memset (p4, 0, 1); + memset (p4, 0, 2); + memset (p4, 0, 3); + memset (p4, 0, 4); + memset (p4, 0, 5); // { dg-warning "memset' writing 5 bytes into a region of size 4" } + } + + { + char a4_1[4]; // { dg-message "destination object 'a4_1" "note" } + char a4_2[4]; // { dg-message "destination object 'a4_2" "note" } + char *p4 = cond1 ? a4_1 : a4_2; + char *p4_i = p4 + i; + + memset (p4_i, 0, 5); // { dg-warning "memset' writing 5 bytes into a region of size 4" } + } + + { + if (i < 1) + i = 1; + + char a4_1[4]; // { dg-message "at offset \\\[1, 4] into destination object 'a4_1" "note" } + char a4_2[4]; // { dg-message "at offset \\\[1, 4] into destination object 'a4_2" "note" } + char *p4 = cond1 ? a4_1 : a4_2; + char *p4_i = p4 + i; + + memset (p4_i, 0, 3); + memset (p4_i, 0, 4); // { dg-warning "memset' writing 4 bytes into a region of size 3 " } + } +} + + +void memset_decl_2_off (void) +{ + int i1 = SR (1, INT_MAX); + int i2 = SR (2, INT_MAX); + + { + char a5[5]; // { dg-warning "at offset [1, 5] into destination object 'a5' + char a7[7]; // { dg-warning "at offset [2, 7] into destination object 'a7' + char *p5_p1 = a5 + i1; + char *p7_p2 = a7 + i2; + char *p5_7 = cond1 ? p5_p1 : p7_p2; + + memset (p5_7, 0, 1); + memset (p5_7, 0, 2); + memset (p5_7, 0, 3); + memset (p5_7, 0, 4); + memset (p5_7, 0, 5); + memset (p5_7, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + int i3 = SR (3, INT_MAX); + + { + char a5[5]; + // { dg-message "at offset \\\[3, 5] into destination object 'a5'" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[2, 5] into destination object 'a5'" "note" { target *-*-* } .-2 } + // { dg-message "at offset \\\[1, 5] into destination object 'a5'" "note" { target *-*-* } .-3 } + // { dg-message ": destination object 'a5'" "note" { target *-*-* } .-4 } + char a9[9]; + // { dg-message "at offset \\\[4, 9] into destination object 'a9'" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[3, 9] into destination object 'a9'" "note" { target *-*-* } .-2 } + // { dg-message "at offset \\\[2, 9] into destination object 'a9'" "note" { target *-*-* } .-3 } + // { dg-message ": destination object 'a9'" "note" { target *-*-* } .-4 } + char *p5_p2 = a5 + i2; // 3 bytes left + char *p9_p3 = a9 + i3; // 6 bytes left + char *p = + cond1 ? p5_p2 : p9_p3; // [3 - 6] bytes left + char *q = p + i1; // [2 - 5] bytes left + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); + memset (q, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5" } + + --q; // [3 - 6] bytes left + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); + memset (q, 0, 6); + memset (q, 0, 7); // { dg-warning "memset' writing 7 bytes into a region of size 6" } + + --q; // [4 - 7] bytes left + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); + memset (q, 0, 6); + memset (q, 0, 7); + memset (q, 0, 8); // { dg-warning "memset' writing 8 bytes into a region of size 7" } + + int m1_x = SR (-1, INT_MAX); + int m2_x = SR (-2, INT_MAX); + + q += cond2 ? m1_x : m2_x; // [5 - 9] bytes left + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); + memset (q, 0, 6); + memset (q, 0, 7); + memset (q, 0, 8); + memset (q, 0, 9); + memset (q, 0, 10); // { dg-warning "memset' writing 10 bytes into a region of size 9" } + } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-59.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-59.c new file mode 100644 index 00000000000..c45a92d21e1 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-59.c @@ -0,0 +1,267 @@ +/* PR middle-end/92936 - missing warning on a past-the-end store to a PHI + Exercise warnings for writing into one of two or more allocated objects. + { dg-do compile } + { dg-options "-O2 -Wall -Wno-array-bounds -ftrack-macro-expansion=0" } */ + +#include "range.h" + +#define INT_MAX __INT_MAX__ + +extern void* malloc (size_t); +extern void* memset (void*, int, size_t); +#define memset(d, c, n) sink (memset (d, c, n)) + +void sink (int, ...); +#define sink(...) sink (0, __VA_ARGS__) + +volatile int cond1, cond2, x; + +#define CHOOSE_MALLOC_2(n1, n2) \ + (cond1 ? malloc (n1) : malloc (n2)) +#define CHOOSE_MALLOC_3(n1, n2, n3) \ + (cond1 < 0 ? malloc (n1) : 0 < cond1 ? malloc (n2) : malloc (n3)) + + +void memset_malloc_2 (void) +{ + { + char *p0_1 = CHOOSE_MALLOC_2 (0, 1); + + memset (p0_1, 0, 0); + /* Writing more than the smallest destination should trigger a "may + write" warning if the access is unconditionally reachable from + the block where the pointer to either object is assigned. */ + memset (p0_1, 0, 1); + memset (p0_1, 0, 2); // { dg-warning "memset' writing 2 bytes into a region of size 1 " } + memset (p0_1, 0, 9); // { dg-warning "memset' writing 9 bytes into a region of size 1 " } + } + + { + char *p0_x = CHOOSE_MALLOC_2 (0, x); + + memset (p0_x, 0, 0); + memset (p0_x, 0, 1); + memset (p0_x, 0, 2); + memset (p0_x, 0, 12345); + } + + { + char *px_x = CHOOSE_MALLOC_2 (x, x); + + memset (px_x, 0, 0); + memset (px_x, 0, 1); + memset (px_x, 0, 2); + memset (px_x, 0, 12345); + } + + { + char *p3_5 = CHOOSE_MALLOC_2 (3, 5); + + memset (p3_5, 0, 1); + memset (p3_5, 0, 3); + memset (p3_5, 0, 4); + memset (p3_5, 0, 5); + memset (p3_5, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + { + char *p5_3 = CHOOSE_MALLOC_2 (5, 3); + + memset (p5_3, 0, 3); + memset (p5_3, 0, 4); + memset (p5_3, 0, 5); + memset (p5_3, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + { + char *px_3 = CHOOSE_MALLOC_2 (x, 3); + + memset (px_3, 0, 1); + memset (px_3, 0, 3); + memset (px_3, 0, 4); + memset (px_3, 0, 1234); + } + + { + char *p5_x = CHOOSE_MALLOC_2 (5, x); + + memset (p5_x, 0, 1); + memset (p5_x, 0, 5); + memset (p5_x, 0, 6); + memset (p5_x, 0, 1234); + } + +} + + +void memset_malloc_3 (void) +{ + { + char *p0_1_2 = CHOOSE_MALLOC_3 (0, 1, 2); + memset (p0_1_2, 0, 0); + memset (p0_1_2, 0, 1); + memset (p0_1_2, 0, 2); + memset (p0_1_2, 0, 3); // { dg-warning "memset' writing 3 bytes into a region of size 2 " } + memset (p0_1_2, 0, 9); // { dg-warning "memset' writing 9 bytes into a region of size 2 " } + } + + { + char *p0_2_x = CHOOSE_MALLOC_3 (0, 2, x); + + memset (p0_2_x, 0, 0); + memset (p0_2_x, 0, 1); + memset (p0_2_x, 0, 3); + memset (p0_2_x, 0, 9); + } + + { + char *p3_4_5 = CHOOSE_MALLOC_3 (3, 4, 5); + + memset (p3_4_5, 0, 3); + memset (p3_4_5, 0, 4); + memset (p3_4_5, 0, 5); + memset (p3_4_5, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + { + char *p5_3_4 = CHOOSE_MALLOC_3 (5, 3, 4); + + memset (p5_3_4, 0, 3); + memset (p5_3_4, 0, 4); + memset (p5_3_4, 0, 5); + memset (p5_3_4, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + { + char *p9_8_7 = CHOOSE_MALLOC_3 (9, 8, 7); + + memset (p9_8_7, 0, 7); + memset (p9_8_7, 0, 8); + memset (p9_8_7, 0, 9); + memset (p9_8_7, 0, 10); // { dg-warning "memset' writing 10 bytes into a region of size 9 " } + } +} + + +/* Verify conditionally writing into one of two objects with the same + size. */ + +void memset_malloc_2_same_size (int i) +{ + { + char a4_1[4], a4_2[4]; + char *p4 = cond1 ? a4_1 : a4_2; + + memset (p4, 0, 1); + memset (p4, 0, 2); + memset (p4, 0, 3); + memset (p4, 0, 4); + memset (p4, 0, 5); // { dg-warning "memset' writing 5 bytes into a region of size 4" } + } + + { + char a4_1[4]; // { dg-message "destination object 'a4_1" "note" } + char a4_2[4]; // { dg-message "destination object 'a4_2" "note" } + char *p4 = cond1 ? a4_1 : a4_2; + char *p4_i = p4 + i; + + memset (p4_i, 0, 5); // { dg-warning "memset' writing 5 bytes into a region of size 4" } + } + + { + if (i < 1) + i = 1; + + char a4_1[4]; // { dg-message "at offset \\\[1, 4] into destination object 'a4_1" "note" } + char a4_2[4]; // { dg-message "at offset \\\[1, 4] into destination object 'a4_2" "note" } + char *p4 = cond1 ? a4_1 : a4_2; + char *p4_i = p4 + i; + + memset (p4_i, 0, 3); + memset (p4_i, 0, 4); // { dg-warning "memset' writing 4 bytes into a region of size 3 " } + } +} + + +void memset_malloc_2_off (void) +{ + int i1 = SR (1, INT_MAX); + int i2 = SR (2, INT_MAX); + + { + char a5[5]; // { dg-warning "at offset [1, 5] into destination object 'a5' + char a7[7]; // { dg-warning "at offset [2, 7] into destination object 'a7' + char *p5_p1 = a5 + i1; + char *p7_p2 = a7 + i2; + char *p5_7 = cond1 ? p5_p1 : p7_p2; + + memset (p5_7, 0, 1); + memset (p5_7, 0, 2); + memset (p5_7, 0, 3); + memset (p5_7, 0, 4); + memset (p5_7, 0, 5); + memset (p5_7, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5 " } + } + + int i3 = SR (3, INT_MAX); + + { + char a5[5]; + // { dg-message "at offset \\\[3, 5] into destination object 'a5'" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[2, 5] into destination object 'a5'" "note" { target *-*-* } .-2 } + // { dg-message "at offset \\\[1, 5] into destination object 'a5'" "note" { target *-*-* } .-3 } + // { dg-message ": destination object 'a5'" "note" { target *-*-* } .-4 } + char a9[9]; + // { dg-message "at offset \\\[4, 9] into destination object 'a9'" "note" { target *-*-* } .-1 } + // { dg-message "at offset \\\[3, 9] into destination object 'a9'" "note" { target *-*-* } .-2 } + // { dg-message "at offset \\\[2, 9] into destination object 'a9'" "note" { target *-*-* } .-3 } + // { dg-message ": destination object 'a9'" "note" { target *-*-* } .-4 } + char *p5_p2 = a5 + i2; // 3 bytes left + char *p9_p3 = a9 + i3; // 6 bytes left + char *p = + cond1 ? p5_p2 : p9_p3; // [3 - 6] bytes left + char *q = p + i1; // [2 - 5] bytes left + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); + memset (q, 0, 6); // { dg-warning "memset' writing 6 bytes into a region of size 5" } + + --q; // [3 - 6] bytes left + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); + memset (q, 0, 6); + memset (q, 0, 7); // { dg-warning "memset' writing 7 bytes into a region of size 6" } + + --q; // [4 - 7] bytes left + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); + memset (q, 0, 6); + memset (q, 0, 7); + memset (q, 0, 8); // { dg-warning "memset' writing 8 bytes into a region of size 7" } + + int m1_x = SR (-1, INT_MAX); + int m2_x = SR (-2, INT_MAX); + + q += cond2 ? m1_x : m2_x; // [5 - 9] bytes left + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); + memset (q, 0, 6); + memset (q, 0, 7); + memset (q, 0, 8); + memset (q, 0, 9); + memset (q, 0, 10); // { dg-warning "memset' writing 10 bytes into a region of size 9" } + } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-60.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-60.c new file mode 100644 index 00000000000..8c9de20e9bc --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-60.c @@ -0,0 +1,72 @@ +/* Test derived from Glibc's getifaddrs_internal. The code could be + rewritten to avoid the warning for the memcpy call but since unions + are designed to have their members treated as interchangeable there + isn't a whole lot to be gained from issuing one. + { dg-do compile } + { dg-options "-O2 -Wall" } */ + +typedef __SIZE_TYPE__ size_t; + +extern void* memcpy (void*, const void*, size_t); + +struct sockaddr +{ + short sa_family; + char sa_data[14]; +}; + +struct in_addr +{ + int s_addr; +}; + +struct in6_addr +{ + union + { + char __u6_addr8[16]; + short __u6_addr16[8]; + int __u6_addr32[4]; + } __in6_u; +}; + +struct sockaddr_in +{ + short sin_family; + short sin_port; + struct in_addr sin_addr; + unsigned char sin_zero[sizeof (struct sockaddr) - + (sizeof (short)) - + sizeof (short) - + sizeof (struct in_addr)]; +}; + +struct sockaddr_in6 +{ + short sin6_family; + short sin6_port; + int sin6_flowinfo; + struct in6_addr sin6_addr; + int sin6_scope_id; +}; + +union +{ + struct sockaddr sa; + struct sockaddr_in s4; + struct sockaddr_in6 s6; +} u1, u2; + +struct sockaddr *sa; + +void test_unconditional (void *p) +{ + sa = &u1.sa; + memcpy (&((struct sockaddr_in6 *) sa)->sin6_addr, p, 16); +} + +void test_conditional (void *p, int i) +{ + sa = i ? &u1.sa : &u2.sa; + memcpy (&((struct sockaddr_in6 *) sa)->sin6_addr, p, 16); +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-61.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-61.c new file mode 100644 index 00000000000..7601679fac3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-61.c @@ -0,0 +1,88 @@ +/* { dg-do compile } + { dg-options "-O2 -Wall" } */ + +typedef __SIZE_TYPE__ size_t; + +void* malloc (size_t); +void* memcpy (void*, const void*, size_t); +size_t strlen (const char *); + +// Test case reduced from gcc/attribs.c. + +char* sorted_attr_string (char *argv[]) +{ + size_t n = 0; + unsigned int i; + + for (i = 0; argv[i]; ++i) + n += strlen (argv[i]); + + char *s = (char*)malloc (n); + n = 0; + for (i = 0; argv[i]; ++i) + { + const char *str = argv[i]; + size_t len = strlen (str); + memcpy (s + n, str, len); + n += len + 1; + } + + /* Replace "=,-" with "_". */ + for (i = 0; i < strlen (s); i++) + if (s[i] == '=') + s[i] = '_'; // { dg-bogus "\\\[-Wstringop-overflow" } + + return s; +} + + +void f (void*); + +void nowarn_cond_escape (int c, int *x) +{ + extern char a3[3], a5[5]; + + char *p; + if (c) + { + p = a3; + *x = 2; + } + else + { + p = a5; + *x = 4; + } + + f (p); // may modify *x + + if (*x == 2) + p[2] = 0; + else if (*x == 4) + p[4] = 0; // { dg-bogus "\\\[-Wstringop-overflow" } +} + +void warn_cond_escape (int c, int *x) +{ + extern char a3_2[3]; + extern char a5_2[5]; // { dg-message "at offset 5 into destination object 'a5_2'" } + + char *p; + if (c) + { + p = a3_2; + *x = 2; + } + else + { + p = a5_2; + *x = 5; + } + + f (p); // may modify *x + + if (*x == 2) + p[2] = 0; + else if (*x == 5) + p[5] = 0; // { dg-warning "\\\[-Wstringop-overflow" } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-62.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-62.c new file mode 100644 index 00000000000..318d9bd1f94 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-62.c @@ -0,0 +1,363 @@ +/* Test for MIN and MAX expressions involving pointers. + { dg-do compile } + { dg-options "-O2 -Wall -Wno-array-bounds -ftrack-macro-expansion=0" } */ + +#include "range.h" + +#define INT_MAX __INT_MAX__ + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) < (y) ? (y) : (x)) + +typedef __SIZE_TYPE__ size_t; + +void* memset (void*, int, size_t); +#define memset(...) sink (memset (__VA_ARGS__)) + +void sink (void*, ...); + +volatile int cond, vi; +char* volatile ptr; + +void test_min (void) +{ + const int i1 = SR (1, INT_MAX); + const int i2 = SR (2, INT_MAX); + + { + /* Exercise both pointers pointing to a different unknown object plus + positive constant offset. Since PTR is volatile P1 and P2 cannot + normally be considered to point to the same object. It can only + be inferred from the MIN expression. */ + char *p1 = ptr + 1; + char *p2 = ptr + 2; + + char *q = MIN (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, INT_MAX); + // { dg-warning "writing 2147483647 bytes into a region of size 2147483646" "ilp32" { target ilp32 } .-1 } + memset (q, 0, DIFF_MAX - 2); + memset (q, 0, DIFF_MAX); + // { dg-warning "writing 2147483647 bytes into a region of size 2147483646" "ilp32" { target ilp32 } .-1 } + // { dg-warning "writing 9223372036854775807 bytes into a region of size 9223372036854775806" "lp64" { target lp64 } .-2 } + } + + { + /* Exercise both pointers pointing to a different unknown object plus + variable offset. */ + char *p1 = ptr + vi; + char *p2 = ptr + vi; + + char *q = MIN (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, INT_MAX); + } + + { + /* Exercise both pointers pointing to the same object plus constant + offset. */ + char a2[2]; // { dg-message "at offset 1 into destination object 'a2' of size 2" "note" } + char *p1 = a2 + 1; + char *p2 = a2 + 2; + + char *q = MIN (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); // { dg-warning "writing 2 bytes into a region of size 1 " } + } + + { + /* Exercise both pointers pointing to the same object plus offset + in a known range. */ + char a3[3]; // { dg-message "at offset \\\[1, 3] into destination object 'a3'" "note" } + char *pi = a3 + i1; + char *pj = a3 + i2; + + char *q = MIN (pi, pj); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); // { dg-warning "writing 3 bytes into a region of size 2 " } + } + + { + /* Exercise both pointers pointing to the same object plus variable + offset. Verify that no offset is mentioned in the note (since + its unknown, printing its full range is unnecessary). */ + char a4[4]; // { dg-message ": destination object 'a4'" "note" } + char *pi = a4 + vi; + char *pj = a4 + vi; + + char *q = MIN (pi, pj); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); // { dg-warning "writing 5 bytes into a region of size 4 " } + } + + { + /* Exercise a pointer pointing to a known object with one pointing + to an unknown object. */ + char a5[5]; // { dg-message ": destination object 'a5'" "note" } + char *p = ptr; + char *q = MIN (p, a5); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 5); + memset (q, 0, 6); // { dg-warning "writing 6 bytes into a region of size 5 " } + } + + { + /* Exercise a pointer pointing to a known object plus constant offset + with one pointing to an unknown object. */ + char a6[6]; // { dg-message ": destination object 'a6'" "note" } + char *p1 = ptr; + char *p2 = a6 + 1; + char *q = MIN (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 6); + memset (q, 0, 7); // { dg-warning "writing 7 bytes into a region of size 6 " } + } + + { + /* Exercise a pointer pointing to a known object with one pointing + to an unknown object plus constant offset. */ + char a7[7]; // { dg-message ": destination object 'a7'" "note" } + char *p1 = a7; + char *p2 = ptr + 1; + /* Since p1 points to a7[0] it must be less than any pointer to a7 + plus positive offset, and so Q == P1. */ + char *q = MIN (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 7); + memset (q, 0, 8); // { dg-warning "writing 8 bytes into a region of size 7 " } + } + + { + /* Exercise a pointer pointing to a known object plus constant offset + with one pointing to an unknown object plus a different constant + offset. */ + char a8[8]; // { dg-message "at offset 1 into destination object 'a8'" "note" } + char *p1 = a8 + 1; + char *p2 = ptr + 2; + /* Since P1 points to A8[1] it must be less than or equal to any + pointer to A8 plus positive offset. Either way, Q must point + to A8[1]. */ + char *q = MIN (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 7); + memset (q, 0, 8); // { dg-warning "writing 8 bytes into a region of size 7 " } + } + + { + /* Same as above but with larger offsets. */ + char a9[9]; // { dg-message "at offset 3 into destination object 'a9'" "note" } + char *p1 = a9 + 3; + char *p2 = ptr + 4; + /* Since P1 points to A9[3] it must be less than or equal to any + pointer anywhere into A9 plus 4, so Q must point to A9[3]. */ + char *q = MIN (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 6); + memset (q, 0, 7); // { dg-warning "writing 7 bytes into a region of size 6 " } + } + + { + /* Same as above but with the offsets reversed. */ + char a10[10]; // { dg-message "at offset 5 into destination object 'a10'" "note" } + char *p1 = a10 + 10; + char *p2 = ptr + 5; + /* Since P1 points just past the end of A10 it could be either less + or equal to another pointer anywhere into A10 plus 3 because + the other pointer itself could start at a non-zero offset that's + not reflected in the determined offset). */ + char *q = MIN (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 5); + memset (q, 0, 6); // { dg-warning "writing 6 bytes into a region of size 5 " } + } + + { + char a3[3]; // { dg-message ": destination object 'a3'" "note" } + char *p1 = ptr; + char *p2 = a3 + i1; + char *q = MIN (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); // { dg-warning "writing 4 bytes into a region of size 3 " } + } +} + + +void test_max (void) +{ + const int i1 = SR (1, INT_MAX); + const int i2 = SR (2, INT_MAX); + + { + /* Exercise both pointers pointing to the same object plus constant + offset. */ + char a2[2]; // { dg-message "at offset 1 into destination object 'a2' of size 2" "note" } + char *pi = a2 + 1; + char *pj = a2 + 2; + + char *q = MAX (pi, pj); + + memset (q, 0, 1); + memset (q, 0, 2); // { dg-warning "writing 2 bytes into a region of size 1 " } + } + + { + /* Exercise both pointers pointing to the same object plus offset + in a known range. */ + char a3[3]; // { dg-message "at offset \\\[1, 3] into destination object 'a3'" "note" } + char *pi = a3 + i1; + char *pj = a3 + i2; + + char *q = MAX (pi, pj); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); // { dg-warning "writing 3 bytes into a region of size 2 " } + } + + { + /* Exercise both pointers pointing to the same object plus variable + offset. Verify that no offset is mentioned in the note (since + its unknown, printing its full range is unnecessary). */ + char a4[4]; // { dg-message ": destination object 'a4'" "note" } + char *pi = a4 + vi; + char *pj = a4 + vi; + + char *q = MAX (pi, pj); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 3); + memset (q, 0, 4); + memset (q, 0, 5); // { dg-warning "writing 5 bytes into a region of size 4 " } + } + + { + /* Exercise a pointer pointing to a known object with one pointing + to an unknown object. */ + char a5[5]; // { dg-message ": destination object 'a5'" "note" } + char *p = ptr; + char *q = MAX (p, a5); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 5); + memset (q, 0, 6); // { dg-warning "writing 6 bytes into a region of size 5 " } + } + + { + /* Exercise a pointer pointing to a known object plus constant offset + with one pointing to an unknown object. */ + char a6[6]; // { dg-message "at offset 1 into destination object 'a6'" "note" } + char *p1 = ptr; + char *p2 = a6 + 1; + char *q = MAX (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 5); + memset (q, 0, 6); // { dg-warning "writing 6 bytes into a region of size 5 " } + memset (q, 0, 7); // { dg-warning "writing 7 bytes into a region of size 5 " } + } + + { + /* Exercise a pointer pointing to a known object with one pointing + to an unknown object plus constant offset. */ + char a7[7]; // { dg-message "at offset 1 into destination object 'a7'" "note" } + char *p1 = a7; + char *p2 = ptr + 1; + /* Since p1 points to a7[0] it must be less than any pointer to a7 + plus positive offset, and so Q == P2. */ + char *q = MAX (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 6); + memset (q, 0, 7); // { dg-warning "writing 7 bytes into a region of size 6 " } + memset (q, 0, 8); // { dg-warning "writing 8 bytes into a region of size 6 " } + } + + { + /* Exercise a pointer pointing to a known object plus constant offset + with one pointing to an unknown object plus a different constant + offset. */ + char a8[8]; // { dg-message "at offset 2 into destination object 'a8'" "note" } + char *p1 = a8 + 1; + char *p2 = ptr + 2; + /* Since P1 points to A8[1] it must be less than or equal to any + pointer to A8 plus positive offset. Either way, Q must point + to A8[2]. */ + char *q = MAX (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 6); + memset (q, 0, 7); // { dg-warning "writing 7 bytes into a region of size 6 " } + memset (q, 0, 8); // { dg-warning "writing 8 bytes into a region of size 6 " } + } + + { + /* Same as above but with larger offsets. */ + char a9[9]; // { dg-message "at offset 4 into destination object 'a9'" "note" } + char *p1 = a9 + 3; + char *p2 = ptr + 4; + /* Since P1 points to A9[3] it must be less than or equal to any + pointer anywhere into A9 plus 4, so Q must point to A9[4]. */ + char *q = MAX (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 5); + memset (q, 0, 6); // { dg-warning "writing 6 bytes into a region of size 5 " } + } + + { + /* Same as above but with the offsets reversed. */ + char a10[10]; // { dg-message "at offset 10 into destination object 'a10'" "note" } + char *p1 = a10 + 10; + char *p2 = ptr + 5; + /* Since P1 points just past the end of A10 it could be either less + or equal to another pointer anywhere into A10 plus 3 because + the other pointer itself could start at a non-zero offset that's + not reflected in the determaxed offset). */ + char *q = MAX (p1, p2); + + memset (q, 0, 1); // { dg-warning "writing 1 byte into a region of size 0 " } + } + + { + char a11[11]; // { dg-message "at offset \\\[1, 11] into destination object 'a11'" "note" } + char *p1 = ptr; + char *p2 = a11 + i1; + char *q = MAX (p1, p2); + + memset (q, 0, 1); + memset (q, 0, 2); + memset (q, 0, 10); + memset (q, 0, 11); // { dg-warning "writing 11 bytes into a region of size 10 " } + } +} + diff --git a/gcc/testsuite/gcc.dg/warn-strnlen-no-nul.c b/gcc/testsuite/gcc.dg/warn-strnlen-no-nul.c index 02f6f3d5342..82555b0fbcc 100644 --- a/gcc/testsuite/gcc.dg/warn-strnlen-no-nul.c +++ b/gcc/testsuite/gcc.dg/warn-strnlen-no-nul.c @@ -143,11 +143,11 @@ T (v0 ? b[1] : "", bsz); T (v0 ? b[2] : "", bsz); T (v0 ? b[3] : "", bsz); -T (v0 ? "" : b[0], bsz + 1); +T (v0 ? "" : b[0], bsz + 1); /* { dg-warning "bound 6 may exceed source size 5" } */ T (v0 ? "" : b[1], bsz + 1); T (v0 ? "" : b[2], bsz + 1); T (v0 ? "" : b[3], bsz + 1); /* { dg-warning "unterminated" "pr86937" { xfail *-*-* } } */ -T (v0 ? b[0] : "", bsz + 1); +T (v0 ? b[0] : "", bsz + 1); /* { dg-warning "bound 6 may exceed source size 5" } */ T (v0 ? b[1] : "", bsz + 1); T (v0 ? b[2] : "", bsz + 1); T (v0 ? b[3] : "", bsz + 1); /* { dg-warning "unterminated" "pr86937" { xfail *-*-* } } */ @@ -185,8 +185,8 @@ T (v0 ? "1234" : b[i3], bsz + 1); /* { dg-warning "unterminated" "pr86937" { xfa T (v0 ? b[3] : "1234", bsz + 1); /* { dg-warning "unterminated" "pr86937" { xfail *-*-* } } */ T (v0 ? b[i3] : "1234", bsz + 1); /* { dg-warning "unterminated" "pr86937" { xfail *-*-* } } */ -T (v0 ? a : b[3], bsz + 1); /* { dg-warning "unterminated" "pr86937" { xfail *-*-* } } */ -T (v0 ? b[0] : b[2], bsz + 1); +T (v0 ? a : b[3], bsz + 1); /* { dg-warning "bound 6 may exceed source size 5" "pr86937" } */ +T (v0 ? b[0] : b[2], bsz + 1); /* { dg-warning "bound 6 may exceed source size 5" "pr86937" } */ T (v0 ? b[2] : b[3], bsz + 1); /* { dg-warning "unterminated" "pr86937" { xfail *-*-* } } */ T (v0 ? b[3] : b[2], bsz + 1); /* { dg-warning "unterminated" "pr86937" { xfail *-*-* } } */ diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index ebb17cd852c..7de2daa2b54 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -1933,6 +1933,8 @@ maybe_warn_overflow (gimple *stmt, tree len, dest = gimple_call_arg (stmt, 0); writefn = gimple_call_fndecl (stmt); } + else + return; if (TREE_NO_WARNING (dest)) return; @@ -1941,148 +1943,22 @@ maybe_warn_overflow (gimple *stmt, tree len, Make sure all operands have the same precision to keep wide_int from ICE'ing. */ - /* Convenience constants. */ - const widest_int diff_min - = wi::to_widest (TYPE_MIN_VALUE (ptrdiff_type_node)); - const widest_int diff_max - = wi::to_widest (TYPE_MAX_VALUE (ptrdiff_type_node)); - const widest_int size_max - = wi::to_widest (TYPE_MAX_VALUE (size_type_node)); - - /* The offset into the destination object computed below and not - reflected in DESTSIZE. */ - widest_int offrng[2] = { 0, 0 }; - - if (!si) - { - /* If no destination STRINFO was provided try to get it from - the DEST argument. */ - tree ref = dest; - if (TREE_CODE (ref) == ARRAY_REF) - { - /* Handle stores to VLAs (represented as - ARRAY_REF (MEM_REF (vlaptr, 0), N]. */ - tree off = TREE_OPERAND (ref, 1); - ref = TREE_OPERAND (ref, 0); - wide_int rng[2]; - if (get_range (off, stmt, rng, rvals)) - { - /* Convert offsets to the maximum precision. */ - offrng[0] = widest_int::from (rng[0], SIGNED); - offrng[1] = widest_int::from (rng[1], SIGNED); - } - else - { - offrng[0] = diff_min; - offrng[1] = diff_max; - } - } - - if (TREE_CODE (ref) == MEM_REF) - { - tree mem_off = TREE_OPERAND (ref, 1); - ref = TREE_OPERAND (ref, 0); - wide_int rng[2]; - if (get_range (mem_off, stmt, rng, rvals)) - { - offrng[0] += widest_int::from (rng[0], SIGNED); - offrng[1] += widest_int::from (rng[1], SIGNED); - } - else - { - offrng[0] = diff_min; - offrng[1] = diff_max; - } - } - - wide_int rng[2]; - if (int idx = get_stridx (ref, rng, rvals)) - { - si = get_strinfo (idx); - offrng[0] += widest_int::from (rng[0], SIGNED); - offrng[1] += widest_int::from (rng[1], SIGNED); - } - } - - /* The allocation call if the destination object was allocated - by one. */ - gimple *alloc_call = NULL; - /* The DECL of the destination object if known and not dynamically - allocated. */ - tree destdecl = NULL_TREE; - /* The offset into the destination object set by compute_objsize - but already reflected in DESTSIZE. */ - tree destoff = NULL_TREE; + access_ref aref; /* The size of the destination region (which is smaller than the destination object for stores at a non-zero offset). */ - tree destsize = NULL_TREE; - - /* Compute the range of sizes of the destination object. The range - is constant for declared objects but may be a range for allocated - objects. */ - widest_int sizrng[2] = { 0, 0 }; - if (si) - { - wide_int rng[2]; - destsize = gimple_call_alloc_size (si->alloc, rng, rvals); - if (destsize) - { - sizrng[0] = widest_int::from (rng[0], UNSIGNED); - sizrng[1] = widest_int::from (rng[1], UNSIGNED); - } - alloc_call = si->alloc; - } - else - offrng[0] = offrng[1] = 0; - + tree destsize = compute_objsize (dest, rawmem ? 0 : 1, &aref, rvals); if (!destsize) { - /* If there is no STRINFO for DEST, fall back on compute_objsize. */ - tree off = NULL_TREE; - destsize = compute_objsize (dest, rawmem ? 0 : 1, &destdecl, &off, rvals); - if (destsize) - { - /* Remember OFF but clear OFFRNG that may have been set above. */ - destoff = off; - offrng[0] = offrng[1] = 0; - - if (destdecl && TREE_CODE (destdecl) == SSA_NAME) - { - gimple *stmt = SSA_NAME_DEF_STMT (destdecl); - if (is_gimple_call (stmt)) - alloc_call = stmt; - destdecl = NULL_TREE; - } - - wide_int rng[2]; - if (get_range (destsize, stmt, rng, rvals)) - { - sizrng[0] = widest_int::from (rng[0], UNSIGNED); - sizrng[1] = widest_int::from (rng[1], UNSIGNED); - } - else - { - /* On failure, rather than failing, set the maximum range - so that overflow in allocated objects whose size depends - on the strlen of the source can still be diagnosed - below. */ - sizrng[0] = 0; - sizrng[1] = size_max; - } - } + aref.sizrng[0] = 0; + aref.sizrng[1] = wi::to_offset (max_object_size ()); } - if (!destsize) - { - sizrng[0] = 0; - sizrng[1] = size_max; - }; - /* Return early if the DESTSIZE size expression is the same as LEN and the offset into the destination is zero. This might happen in the case of a pair of malloc and memset calls to allocate an object and clear it as if by calloc. */ - if (destsize == len && !plus_one && offrng[0] == 0 && offrng[0] == offrng[1]) + if (destsize == len && !plus_one + && aref.offrng[0] == 0 && aref.offrng[0] == aref.offrng[1]) return; wide_int rng[2]; @@ -2100,38 +1976,13 @@ maybe_warn_overflow (gimple *stmt, tree len, /* The size of the remaining space in the destination computed as the size of the latter minus the offset into it. */ - widest_int spcrng[2] = { sizrng[0], sizrng[1] }; - if (wi::neg_p (offrng[0]) && wi::neg_p (offrng[1])) - { - /* When the offset is negative and the size of the destination - object unknown there is little to do. - FIXME: Detect offsets that are necessarily invalid regardless - of the size of the object. */ - if (!destsize) - return; - - /* The remaining space is necessarily zero. */ - spcrng[0] = spcrng[1] = 0; - } - else if (wi::neg_p (offrng[0])) - { - /* When the lower bound of the offset is negative but the upper - bound is not, reduce the upper bound of the remaining space - by the upper bound of the offset but leave the lower bound - unchanged. If that makes the upper bound of the space less - than the lower bound swap the two. */ - spcrng[1] -= wi::ltu_p (offrng[1], spcrng[1]) ? offrng[1] : spcrng[1]; - if (wi::ltu_p (spcrng[1], spcrng[0])) - std::swap (spcrng[1], spcrng[0]); - } - else - { - /* When the offset is positive reduce the remaining space by - the lower bound of the offset or clear it if the offset is - greater. */ - spcrng[0] -= wi::ltu_p (offrng[0], spcrng[0]) ? offrng[0] : spcrng[0]; - spcrng[1] -= wi::ltu_p (offrng[0], spcrng[1]) ? offrng[0] : spcrng[1]; - } + widest_int spcrng[2]; + { + offset_int remrng[2]; + remrng[1] = aref.size_remaining (remrng); + spcrng[0] = remrng[0] == -1 ? 0 : widest_int::from (remrng[0], UNSIGNED); + spcrng[1] = widest_int::from (remrng[1], UNSIGNED); + } if (wi::leu_p (lenrng[0], spcrng[0]) && wi::leu_p (lenrng[1], spcrng[1])) @@ -2233,112 +2084,7 @@ maybe_warn_overflow (gimple *stmt, tree len, gimple_set_no_warning (stmt, true); - /* If DESTOFF is not null, use it to format the offset value/range. */ - if (destoff) - { - wide_int rng[2]; - if (get_range (destoff, stmt, rng)) - { - offrng[0] = widest_int::from (rng[0], SIGNED); - offrng[1] = widest_int::from (rng[1], SIGNED); - } - else - offrng[0] = offrng[1] = 0; - } - - /* Format the offset to keep the number of inform calls from growing - out of control. */ - char offstr[64]; - if (offrng[0] == offrng[1]) - sprintf (offstr, "%lli", (long long) offrng[0].to_shwi ()); - else - sprintf (offstr, "[%lli, %lli]", - (long long) offrng[0].to_shwi (), (long long) offrng[1].to_shwi ()); - - if (destdecl && DECL_P (destdecl)) - { - if (tree size = DECL_SIZE_UNIT (destdecl)) - inform (DECL_SOURCE_LOCATION (destdecl), - "at offset %s to object %qD with size %E declared here", - offstr, destdecl, size); - else - inform (DECL_SOURCE_LOCATION (destdecl), - "at offset %s to object %qD declared here", - offstr, destdecl); - return; - } - - if (!alloc_call) - return; - - tree allocfn = gimple_call_fndecl (alloc_call); - if (!allocfn) - { - /* For an ALLOC_CALL via a function pointer make a small effort - to determine the destination of the pointer. */ - allocfn = gimple_call_fn (alloc_call); - if (TREE_CODE (allocfn) == SSA_NAME) - { - gimple *def = SSA_NAME_DEF_STMT (allocfn); - if (gimple_assign_single_p (def)) - { - tree rhs = gimple_assign_rhs1 (def); - if (DECL_P (rhs)) - allocfn = rhs; - else if (TREE_CODE (rhs) == COMPONENT_REF) - allocfn = TREE_OPERAND (rhs, 1); - } - } - } - - if (gimple_call_builtin_p (alloc_call, BUILT_IN_ALLOCA_WITH_ALIGN)) - { - if (sizrng[0] == sizrng[1]) - inform (gimple_location (alloc_call), - "at offset %s to an object with size %wu declared here", - offstr, sizrng[0].to_uhwi ()); - else if (sizrng[0] == 0) - { - /* Avoid printing impossible sizes. */ - if (wi::ltu_p (sizrng[1], diff_max - 2)) - inform (gimple_location (alloc_call), - "at offset %s to an object with size at most %wu " - "declared here", - offstr, sizrng[1].to_uhwi ()); - else - inform (gimple_location (alloc_call), - "at offset %s to an object declared here", offstr); - } - else - inform (gimple_location (alloc_call), - "at offset %s to an object with size between %wu and %wu " - "declared here", - offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi ()); - return; - } - - if (sizrng[0] == sizrng[1]) - inform (gimple_location (alloc_call), - "at offset %s to an object with size %wu allocated by %qE here", - offstr, sizrng[0].to_uhwi (), allocfn); - else if (sizrng[0] == 0) - { - /* Avoid printing impossible sizes. */ - if (wi::ltu_p (sizrng[1], diff_max - 2)) - inform (gimple_location (alloc_call), - "at offset %s to an object with size at most %wu allocated " - "by %qD here", - offstr, sizrng[1].to_uhwi (), allocfn); - else - inform (gimple_location (alloc_call), - "at offset %s to an object allocated by %qE here", - offstr, allocfn); - } - else - inform (gimple_location (alloc_call), - "at offset %s to an object with size between %wu and %wu " - "allocated by %qE here", - offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi (), allocfn); + aref.inform_access (access_write_only); } /* Convenience wrapper for the above. */ @@ -3471,7 +3217,7 @@ handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi, if (olddsi != NULL && !integer_zerop (len)) { - maybe_warn_overflow (stmt, len, rvals, olddsi, false, true); + maybe_warn_overflow (stmt, len, rvals, olddsi, false, false); adjust_last_stmt (olddsi, stmt, false); } @@ -3938,7 +3684,7 @@ handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write, tree memset_size = gimple_call_arg (memset_stmt, 2); /* Check for overflow. */ - maybe_warn_overflow (memset_stmt, memset_size, rvals, NULL, false, true); + maybe_warn_overflow (memset_stmt, memset_size, rvals, NULL, false, false); /* Bail when there is no statement associated with the destination (the statement may be null even when SI1->ALLOC is not). */