The attached patch changes the new access warning pass to use the per-function ranger instance. To do that it makes a number of the global static functions members of the pass (that involved moving one to a later point in the file, increasing the diff; the body of the function hasn't changed otherwise). Still more functions remain. At the same time, the patch also enables the simple pointer_query cache to avoid repeatedly recomputing the properties of related pointers into the same objects, and makes the cache more effective (trunk fails to cache a bunch of intermediate results). Finally, the patch enhances the debugging support for the cache.
Other than the ranger/caching the changes have no user-visible effect. Tested on x86_64-linux. Martin Previous patches in this series: https://gcc.gnu.org/pipermail/gcc-patches/2021-August/577526.html https://gcc.gnu.org/pipermail/gcc-patches/2021-August/576821.html https://gcc.gnu.org/pipermail/gcc-patches/2021-July/575377.html
gcc/ChangeLog: * gimple-ssa-warn-access.cc (check_memop_access): Remove template and make a member function. (maybe_check_dealloc_call): Make a pass_waccess member function. (class pass_waccess): Add and rename members. (pass_waccess::pass_waccess): Adjust to name change. (pass_waccess::~pass_waccess): Same. (check_alloca): Make a member function. (check_alloc_size_call): Same. (check_strcat): Same. (check_strncat): Same. (check_stxcpy): Same. (check_stxncpy): Same. (check_strncmp): Same. (maybe_warn_rdwr_sizes): Rename... (pass_waccess::maybe_check_access_sizes): ...to this. (pass_waccess::check_call): Adjust to name changes. (pass_waccess::maybe_check_dealloc_call): Make a pass_waccess member function. (pass_waccess::execute): Adjust to name changes. * gimple-ssa-warn-access.h (check_memop_access): Remove. * pointer-query.cc (access_ref::phi): Handle null pointer. (access_ref::inform_access): Same. (pointer_query::put_ref): Modify a cached value, not a copy of it. (pointer_query::dump): New function. (compute_objsize_r): Avoid overwriting access_ref::bndrng. Cache more results. * pointer-query.h (pointer_query::dump): Declare. * tree-ssa-strlen.c (printf_strlen_execute): Factor code out into pointer_query::put_ref. gcc/testsuite/ChangeLog: * gcc.dg/Wstringop-overflow-73.c: New test. diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc index 4a2dd9ade77..4473b093f88 100644 --- a/gcc/gimple-ssa-warn-access.cc +++ b/gcc/gimple-ssa-warn-access.cc @@ -1511,41 +1511,6 @@ check_access (tree expr, tree dstwrite, mode, pad); } -/* Helper to determine and check the sizes of the source and the destination - of calls to __builtin_{bzero,memcpy,mempcpy,memset} calls. EXP is the - call expression, DEST is the destination argument, SRC is the source - argument or null, and LEN is the number of bytes. Use Object Size type-0 - regardless of the OPT_Wstringop_overflow_ setting. Return true on success - (no overflow or invalid sizes), false otherwise. */ - -template <class GimpleOrTree> -static bool -check_memop_access (GimpleOrTree expr, tree dest, tree src, tree size) -{ - /* For functions like memset and memcpy that operate on raw memory - try to determine the size of the largest source and destination - object using type-0 Object Size regardless of the object size - type specified by the option. */ - access_data data (expr, access_read_write); - tree srcsize = src ? compute_objsize (src, 0, &data.src) : NULL_TREE; - tree dstsize = compute_objsize (dest, 0, &data.dst); - - return check_access (expr, size, /*maxread=*/NULL_TREE, - srcsize, dstsize, data.mode, &data); -} - -bool -check_memop_access (gimple *stmt, tree dest, tree src, tree size) -{ - return check_memop_access<gimple *>(stmt, dest, src, size); -} - -bool -check_memop_access (tree expr, tree dest, tree src, tree size) -{ - return check_memop_access<tree>(expr, dest, src, size); -} - /* A convenience wrapper for check_access above to check access by a read-only function like puts. */ @@ -2093,135 +2058,6 @@ warn_dealloc_offset (location_t loc, gimple *call, const access_ref &aref) return true; } -/* Issue a warning if a deallocation function such as free, realloc, - or C++ operator delete is called with an argument not returned by - a matching allocation function such as malloc or the corresponding - form of C++ operatorn new. */ - -static void -maybe_check_dealloc_call (gcall *call) -{ - tree fndecl = gimple_call_fndecl (call); - if (!fndecl) - return; - - unsigned argno = fndecl_dealloc_argno (fndecl); - if ((unsigned) call_nargs (call) <= argno) - return; - - tree ptr = gimple_call_arg (call, argno); - if (integer_zerop (ptr)) - return; - - access_ref aref; - if (!compute_objsize (ptr, 0, &aref)) - return; - - tree ref = aref.ref; - if (integer_zerop (ref)) - return; - - tree dealloc_decl = fndecl; - location_t loc = gimple_location (call); - - if (DECL_P (ref) || EXPR_P (ref)) - { - /* Diagnose freeing a declared object. */ - if (aref.ref_declared () - && warning_at (loc, OPT_Wfree_nonheap_object, - "%qD called on unallocated object %qD", - dealloc_decl, ref)) - { - inform (get_location (ref), "declared here"); - return; - } - - /* Diagnose freeing a pointer that includes a positive offset. - Such a pointer cannot refer to the beginning of an allocated - object. A negative offset may refer to it. */ - if (aref.sizrng[0] != aref.sizrng[1] - && warn_dealloc_offset (loc, call, aref)) - return; - } - else if (CONSTANT_CLASS_P (ref)) - { - if (warning_at (loc, OPT_Wfree_nonheap_object, - "%qD called on a pointer to an unallocated " - "object %qE", dealloc_decl, ref)) - { - if (TREE_CODE (ptr) == SSA_NAME) - { - gimple *def_stmt = SSA_NAME_DEF_STMT (ptr); - if (is_gimple_assign (def_stmt)) - { - location_t loc = gimple_location (def_stmt); - inform (loc, "assigned here"); - } - } - return; - } - } - else if (TREE_CODE (ref) == SSA_NAME) - { - /* Also warn if the pointer argument refers to the result - of an allocation call like alloca or VLA. */ - gimple *def_stmt = SSA_NAME_DEF_STMT (ref); - if (is_gimple_call (def_stmt)) - { - bool warned = false; - if (gimple_call_alloc_p (def_stmt)) - { - if (matching_alloc_calls_p (def_stmt, dealloc_decl)) - { - if (warn_dealloc_offset (loc, call, aref)) - return; - } - else - { - tree alloc_decl = gimple_call_fndecl (def_stmt); - const opt_code opt = - (DECL_IS_OPERATOR_NEW_P (alloc_decl) - || DECL_IS_OPERATOR_DELETE_P (dealloc_decl) - ? OPT_Wmismatched_new_delete - : OPT_Wmismatched_dealloc); - warned = warning_at (loc, opt, - "%qD called on pointer returned " - "from a mismatched allocation " - "function", dealloc_decl); - } - } - else if (gimple_call_builtin_p (def_stmt, BUILT_IN_ALLOCA) - || gimple_call_builtin_p (def_stmt, - BUILT_IN_ALLOCA_WITH_ALIGN)) - warned = warning_at (loc, OPT_Wfree_nonheap_object, - "%qD called on pointer to " - "an unallocated object", - dealloc_decl); - else if (warn_dealloc_offset (loc, call, aref)) - return; - - if (warned) - { - tree fndecl = gimple_call_fndecl (def_stmt); - inform (gimple_location (def_stmt), - "returned from %qD", fndecl); - return; - } - } - else if (gimple_nop_p (def_stmt)) - { - ref = SSA_NAME_VAR (ref); - /* Diagnose freeing a pointer that includes a positive offset. */ - if (TREE_CODE (ref) == PARM_DECL - && !aref.deref - && aref.sizrng[0] != aref.sizrng[1] - && aref.offrng[0] > 0 && aref.offrng[1] > 0 - && warn_dealloc_offset (loc, call, aref)) - return; - } - } -} - namespace { const pass_data pass_data_waccess = { @@ -2249,6 +2085,11 @@ class pass_waccess : public gimple_opt_pass virtual bool gate (function *); virtual unsigned int execute (function *); +private: + /* Not copyable or assignable. */ + pass_waccess (pass_waccess &) = delete; + void operator= (pass_waccess &) = delete; + /* Check a call to a built-in function. */ bool check_builtin (gcall *); @@ -2259,17 +2100,25 @@ class pass_waccess : public gimple_opt_pass void check (basic_block); /* Check a call to a function. */ - void check (gcall *); + void check (gcall *); -private: - /* Not copyable or assignable. */ - pass_waccess (pass_waccess &) = delete; - void operator= (pass_waccess &) = delete; + /* Check a call to the named built-in function. */ + void check_alloca (gcall *); + void check_alloc_size_call (gcall *); + void check_strcat (gcall *); + void check_strncat (gcall *); + void check_stxcpy (gcall *); + void check_stxncpy (gcall *); + void check_strncmp (gcall *); + void check_memop_access (gimple *, tree, tree, tree); + + void maybe_check_dealloc_call (gcall *); + void maybe_check_access_sizes (rdwr_map *, tree, tree, gimple *); /* A pointer_query object and its cache to store information about pointers and their targets in. */ - pointer_query ptr_qry; - pointer_query::cache_type var_cache; + pointer_query m_ptr_qry; + pointer_query::cache_type m_var_cache; gimple_ranger *m_ranger; }; @@ -2278,8 +2127,8 @@ private: pass_waccess::pass_waccess (gcc::context *ctxt) : gimple_opt_pass (pass_data_waccess, ctxt), - ptr_qry (m_ranger, &var_cache), - var_cache (), + m_ptr_qry (m_ranger, &m_var_cache), + m_var_cache (), m_ranger () { } @@ -2288,7 +2137,7 @@ pass_waccess::pass_waccess (gcc::context *ctxt) pass_waccess::~pass_waccess () { - ptr_qry.flush_cache (); + m_ptr_qry.flush_cache (); } /* Return true when any checks performed by the pass are enabled. */ @@ -2476,8 +2325,8 @@ maybe_warn_alloc_args_overflow (gimple *stmt, const tree args[2], /* Check a call to an alloca function for an excessive size. */ -static void -check_alloca (gimple *stmt) +void +pass_waccess::check_alloca (gcall *stmt) { if ((warn_vla_limit >= HOST_WIDE_INT_MAX && warn_alloc_size_limit < warn_vla_limit) @@ -2497,8 +2346,8 @@ check_alloca (gimple *stmt) /* Check a call to an allocation function for an excessive size. */ -static void -check_alloc_size_call (gimple *stmt) +void +pass_waccess::check_alloc_size_call (gcall *stmt) { if (gimple_call_num_args (stmt) < 1) /* Avoid invalid calls to functions without a prototype. */ @@ -2547,8 +2396,8 @@ check_alloc_size_call (gimple *stmt) /* Check a call STMT to strcat() for overflow and warn if it does. */ -static void -check_strcat (gimple *stmt) +void +pass_waccess::check_strcat (gcall *stmt) { if (!warn_stringop_overflow && !warn_stringop_overread) return; @@ -2563,8 +2412,8 @@ check_strcat (gimple *stmt) access_data data (stmt, access_read_write, NULL_TREE, true, NULL_TREE, true); const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1; - compute_objsize (src, ost, &data.src); - tree destsize = compute_objsize (dest, ost, &data.dst); + compute_objsize (src, ost, &data.src, &m_ptr_qry); + tree destsize = compute_objsize (dest, ost, &data.dst, &m_ptr_qry); check_access (stmt, /*dstwrite=*/NULL_TREE, /*maxread=*/NULL_TREE, src, destsize, data.mode, &data); @@ -2572,8 +2421,8 @@ check_strcat (gimple *stmt) /* Check a call STMT to strcat() for overflow and warn if it does. */ -static void -check_strncat (gimple *stmt) +void +pass_waccess::check_strncat (gcall *stmt) { if (!warn_stringop_overflow && !warn_stringop_overread) return; @@ -2605,7 +2454,8 @@ check_strncat (gimple *stmt) /* Try to verify that the destination is big enough for the shortest string. First try to determine the size of the destination object into which the source is being copied. */ - tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst); + const int ost = warn_stringop_overflow - 1; + tree destsize = compute_objsize (dest, ost, &data.dst, &m_ptr_qry); /* Add one for the terminating nul. */ tree srclen = (maxlen @@ -2640,8 +2490,8 @@ check_strncat (gimple *stmt) /* Check a call STMT to stpcpy() or strcpy() for overflow and warn if it does. */ -static void -check_stxcpy (gimple *stmt) +void +pass_waccess::check_stxcpy (gcall *stmt) { tree dst = call_arg (stmt, 0); tree src = call_arg (stmt, 1); @@ -2661,8 +2511,8 @@ check_stxcpy (gimple *stmt) access_data data (stmt, access_read_write, NULL_TREE, true, NULL_TREE, true); const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1; - compute_objsize (src, ost, &data.src); - tree dstsize = compute_objsize (dst, ost, &data.dst); + compute_objsize (src, ost, &data.src, &m_ptr_qry); + tree dstsize = compute_objsize (dst, ost, &data.dst, &m_ptr_qry); check_access (stmt, /*dstwrite=*/ NULL_TREE, /*maxread=*/ NULL_TREE, /*srcstr=*/ src, dstsize, data.mode, &data); @@ -2678,8 +2528,8 @@ check_stxcpy (gimple *stmt) /* Check a call STMT to stpncpy() or strncpy() for overflow and warn if it does. */ -static void -check_stxncpy (gimple *stmt) +void +pass_waccess::check_stxncpy (gcall *stmt) { if (!warn_stringop_overflow) return; @@ -2691,8 +2541,8 @@ check_stxncpy (gimple *stmt) access_data data (stmt, access_read_write, len, true, len, true); const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1; - compute_objsize (src, ost, &data.src); - tree dstsize = compute_objsize (dst, ost, &data.dst); + compute_objsize (src, ost, &data.src, &m_ptr_qry); + tree dstsize = compute_objsize (dst, ost, &data.dst, &m_ptr_qry); check_access (stmt, /*dstwrite=*/len, /*maxread=*/len, src, dstsize, data.mode, &data); @@ -2701,8 +2551,8 @@ check_stxncpy (gimple *stmt) /* Check a call STMT to stpncpy() or strncpy() for overflow and warn if it does. */ -static void -check_strncmp (gimple *stmt) +void +pass_waccess::check_strncmp (gcall *stmt) { if (!warn_stringop_overread) return; @@ -2757,8 +2607,8 @@ check_strncmp (gimple *stmt) /* compute_objsize almost never fails (and ultimately should never fail). Don't bother to handle the rare case when it does. */ - if (!compute_objsize (arg1, 1, &adata1.src) - || !compute_objsize (arg2, 1, &adata2.src)) + if (!compute_objsize (arg1, 1, &adata1.src, &m_ptr_qry) + || !compute_objsize (arg2, 1, &adata2.src, &m_ptr_qry)) return; /* Compute the size of the remaining space in each array after @@ -2792,6 +2642,29 @@ check_strncmp (gimple *stmt) } } +/* Determine and check the sizes of the source and the destination + of calls to __builtin_{bzero,memcpy,mempcpy,memset} calls. STMT is + the call statement, DEST is the destination argument, SRC is the source + argument or null, and SIZE is the number of bytes being accessed. Use + Object Size type-0 regardless of the OPT_Wstringop_overflow_ setting. + Return true on success (no overflow or invalid sizes), false otherwise. */ + +void +pass_waccess::check_memop_access (gimple *stmt, tree dest, tree src, tree size) +{ + /* For functions like memset and memcpy that operate on raw memory + try to determine the size of the largest source and destination + object using type-0 Object Size regardless of the object size + type specified by the option. */ + access_data data (stmt, access_read_write); + tree srcsize + = src ? compute_objsize (src, 0, &data.src, &m_ptr_qry) : NULL_TREE; + tree dstsize = compute_objsize (dest, 0, &data.dst, &m_ptr_qry); + + check_access (stmt, size, /*maxread=*/NULL_TREE, + srcsize, dstsize, data.mode, &data); +} + /* Check call STMT to a built-in function for invalid accesses. Return true if a call has been handled. */ @@ -2950,8 +2823,9 @@ append_attrname (const std::pair<int, attr_access> &access, arguments and diagnose past-the-end accesses and related problems in the function call EXP. */ -static void -maybe_warn_rdwr_sizes (rdwr_map *rwm, tree fndecl, tree fntype, gimple *stmt) +void +pass_waccess::maybe_check_access_sizes (rdwr_map *rwm, tree fndecl, tree fntype, + gimple *stmt) { auto_diagnostic_group adg; @@ -3142,7 +3016,7 @@ maybe_warn_rdwr_sizes (rdwr_map *rwm, tree fndecl, tree fntype, gimple *stmt) NULL_TREE, false); access_ref* const pobj = (access.second.mode == access_write_only ? &data.dst : &data.src); - tree objsize = compute_objsize (ptr, 1, pobj); + tree objsize = compute_objsize (ptr, 1, pobj, &m_ptr_qry); /* The size of the destination or source object. */ tree dstsize = NULL_TREE, srcsize = NULL_TREE; @@ -3258,7 +3132,7 @@ pass_waccess::check_call (gcall *stmt) /* Check attribute access arguments. */ tree fndecl = gimple_call_fndecl (stmt); - maybe_warn_rdwr_sizes (&rdwr_idx, fndecl, fntype, stmt); + maybe_check_access_sizes (&rdwr_idx, fndecl, fntype, stmt); check_alloc_size_call (stmt); return true; @@ -3276,6 +3150,138 @@ check_nonstring_args (gcall *stmt) maybe_warn_nonstring_arg (fndecl, stmt); } +/* Issue a warning if a deallocation function such as free, realloc, + or C++ operator delete is called with an argument not returned by + a matching allocation function such as malloc or the corresponding + form of C++ operatorn new. */ + +void +pass_waccess::maybe_check_dealloc_call (gcall *call) +{ + tree fndecl = gimple_call_fndecl (call); + if (!fndecl) + return; + + unsigned argno = fndecl_dealloc_argno (fndecl); + if ((unsigned) call_nargs (call) <= argno) + return; + + tree ptr = gimple_call_arg (call, argno); + if (integer_zerop (ptr)) + return; + + access_ref aref; + if (!compute_objsize (ptr, 0, &aref, &m_ptr_qry)) + return; + + tree ref = aref.ref; + if (integer_zerop (ref)) + return; + + tree dealloc_decl = fndecl; + location_t loc = gimple_location (call); + + if (DECL_P (ref) || EXPR_P (ref)) + { + /* Diagnose freeing a declared object. */ + if (aref.ref_declared () + && warning_at (loc, OPT_Wfree_nonheap_object, + "%qD called on unallocated object %qD", + dealloc_decl, ref)) + { + inform (get_location (ref), "declared here"); + return; + } + + /* Diagnose freeing a pointer that includes a positive offset. + Such a pointer cannot refer to the beginning of an allocated + object. A negative offset may refer to it. */ + if (aref.sizrng[0] != aref.sizrng[1] + && warn_dealloc_offset (loc, call, aref)) + return; + } + else if (CONSTANT_CLASS_P (ref)) + { + if (warning_at (loc, OPT_Wfree_nonheap_object, + "%qD called on a pointer to an unallocated " + "object %qE", dealloc_decl, ref)) + { + if (TREE_CODE (ptr) == SSA_NAME) + { + gimple *def_stmt = SSA_NAME_DEF_STMT (ptr); + if (is_gimple_assign (def_stmt)) + { + location_t loc = gimple_location (def_stmt); + inform (loc, "assigned here"); + } + } + return; + } + } + else if (TREE_CODE (ref) == SSA_NAME) + { + /* Also warn if the pointer argument refers to the result + of an allocation call like alloca or VLA. */ + gimple *def_stmt = SSA_NAME_DEF_STMT (ref); + if (!def_stmt) + return; + + if (is_gimple_call (def_stmt)) + { + bool warned = false; + if (gimple_call_alloc_p (def_stmt)) + { + if (matching_alloc_calls_p (def_stmt, dealloc_decl)) + { + if (warn_dealloc_offset (loc, call, aref)) + return; + } + else + { + tree alloc_decl = gimple_call_fndecl (def_stmt); + const opt_code opt = + (DECL_IS_OPERATOR_NEW_P (alloc_decl) + || DECL_IS_OPERATOR_DELETE_P (dealloc_decl) + ? OPT_Wmismatched_new_delete + : OPT_Wmismatched_dealloc); + warned = warning_at (loc, opt, + "%qD called on pointer returned " + "from a mismatched allocation " + "function", dealloc_decl); + } + } + else if (gimple_call_builtin_p (def_stmt, BUILT_IN_ALLOCA) + || gimple_call_builtin_p (def_stmt, + BUILT_IN_ALLOCA_WITH_ALIGN)) + warned = warning_at (loc, OPT_Wfree_nonheap_object, + "%qD called on pointer to " + "an unallocated object", + dealloc_decl); + else if (warn_dealloc_offset (loc, call, aref)) + return; + + if (warned) + { + tree fndecl = gimple_call_fndecl (def_stmt); + inform (gimple_location (def_stmt), + "returned from %qD", fndecl); + return; + } + } + else if (gimple_nop_p (def_stmt)) + { + ref = SSA_NAME_VAR (ref); + /* Diagnose freeing a pointer that includes a positive offset. */ + if (TREE_CODE (ref) == PARM_DECL + && !aref.deref + && aref.sizrng[0] != aref.sizrng[1] + && aref.offrng[0] > 0 && aref.offrng[1] > 0 + && warn_dealloc_offset (loc, call, aref)) + return; + } + } +} + /* Check call STMT for invalid accesses. */ void @@ -3317,8 +3323,15 @@ pass_waccess::execute (function *fun) FOR_EACH_BB_FN (bb, fun) check (bb); - /* Release the ranger instance and replace it with a global ranger. */ + if (dump_file) + m_ptr_qry.dump (dump_file, (dump_flags & TDF_DETAILS) != 0); + + m_ptr_qry.flush_cache (); + + /* Release the ranger instance and replace it with a global ranger. + Also reset the pointer since calling disable_ranger() deletes it. */ disable_ranger (fun); + m_ranger = NULL; return 0; } diff --git a/gcc/gimple-ssa-warn-access.h b/gcc/gimple-ssa-warn-access.h index 1cd3a28c421..00f5bb1a7b2 100644 --- a/gcc/gimple-ssa-warn-access.h +++ b/gcc/gimple-ssa-warn-access.h @@ -45,7 +45,6 @@ class access_data; extern bool check_access (tree, tree, tree, tree, tree, access_mode, const access_data * = NULL); -extern bool check_memop_access (tree, tree, tree, tree); extern bool check_read_access (gimple *, tree, tree = NULL_TREE, int ost = 1); extern bool check_read_access (tree, tree, tree = NULL_TREE, int = 1); diff --git a/gcc/pointer-query.cc b/gcc/pointer-query.cc index 99caf78bfa7..af622ba04b2 100644 --- a/gcc/pointer-query.cc +++ b/gcc/pointer-query.cc @@ -34,10 +34,13 @@ #include "stringpool.h" #include "attribs.h" #include "gimple-fold.h" +#include "gimple-ssa.h" #include "intl.h" #include "attr-fnspec.h" #include "gimple-range.h" #include "pointer-query.h" +#include "tree-pretty-print.h" +#include "tree-ssanames.h" static bool compute_objsize_r (tree, int, access_ref *, ssa_name_limit_t &, pointer_query *); @@ -628,7 +631,7 @@ access_ref::phi () const return NULL; gimple *def_stmt = SSA_NAME_DEF_STMT (ref); - if (gimple_code (def_stmt) != GIMPLE_PHI) + if (!def_stmt || gimple_code (def_stmt) != GIMPLE_PHI) return NULL; return as_a <gphi *> (def_stmt); @@ -1042,6 +1045,9 @@ access_ref::inform_access (access_mode mode) const if (TREE_CODE (ref) == SSA_NAME) { gimple *stmt = SSA_NAME_DEF_STMT (ref); + if (!stmt) + return; + if (is_gimple_call (stmt)) { loc = gimple_location (stmt); @@ -1343,7 +1349,7 @@ pointer_query::put_ref (tree ptr, const access_ref &ref, int ostype /* = 1 */) if (var_cache->access_refs.length () <= cache_idx) var_cache->access_refs.safe_grow_cleared (cache_idx + 1); - access_ref cache_ref = var_cache->access_refs[cache_idx - 1]; + access_ref &cache_ref = var_cache->access_refs[cache_idx]; if (cache_ref.ref) { gcc_checking_assert (cache_ref.ref == ref.ref); @@ -1364,6 +1370,102 @@ pointer_query::flush_cache () var_cache->access_refs.release (); } +/* Dump statistics and, optionally, cache contents to DUMP_FILE. */ + +void +pointer_query::dump (FILE *dump_file, bool contents /* = false */) +{ + unsigned nused = 0, nrefs = 0; + unsigned nidxs = var_cache->indices.length (); + for (unsigned i = 0; i != nidxs; ++i) + { + unsigned ari = var_cache->indices[i]; + if (!ari) + continue; + + ++nused; + + const access_ref &aref = var_cache->access_refs[ari]; + if (!aref.ref) + continue; + + ++nrefs; + } + + fprintf (dump_file, "pointer_query counters:\n" + " index cache size: %u\n" + " index entries: %u\n" + " access cache size: %u\n" + " access entries: %u\n" + " hits: %u\n" + " misses: %u\n" + " failures: %u\n" + " max_depth: %u\n", + nidxs, nused, + var_cache->access_refs.length (), nrefs, + hits, misses, failures, max_depth); + + if (!contents || !nidxs) + return; + + fputs ("\npointer_query cache contents:\n", dump_file); + + for (unsigned i = 0; i != nidxs; ++i) + { + unsigned ari = var_cache->indices[i]; + if (!ari) + continue; + + const access_ref &aref = var_cache->access_refs[ari]; + if (!aref.ref) + continue; + + /* The level-1 cache index corresponds to the SSA_NAME_VERSION + shifted left by one and ORed with the Object Size Type in + the lowest bit. Print the two separately. */ + unsigned ver = i >> 1; + unsigned ost = i & 1; + + fprintf (dump_file, " %u.%u[%u]: ", ver, ost, ari); + if (tree name = ssa_name (ver)) + { + print_generic_expr (dump_file, name); + fputs (" = ", dump_file); + } + else + fprintf (dump_file, " _%u = ", ver); + + if (gphi *phi = aref.phi ()) + { + fputs ("PHI <", dump_file); + unsigned nargs = gimple_phi_num_args (phi); + for (unsigned i = 0; i != nargs; ++i) + { + tree arg = gimple_phi_arg_def (phi, i); + print_generic_expr (dump_file, arg); + if (i + 1 < nargs) + fputs (", ", dump_file); + } + fputc ('>', dump_file); + } + else + print_generic_expr (dump_file, aref.ref); + + if (aref.offrng[0] != aref.offrng[1]) + fprintf (dump_file, " + [%lli, %lli]", + (long long) aref.offrng[0].to_shwi (), + (long long) aref.offrng[1].to_shwi ()); + else if (aref.offrng[0] != 0) + fprintf (dump_file, " %c %lli", + aref.offrng[0] < 0 ? '-' : '+', + (long long) aref.offrng[0].to_shwi ()); + + fputc ('\n', dump_file); + } + + fputc ('\n', dump_file); +} + /* A helper of compute_objsize_r() to determine the size from an assignment statement STMT with the RHS of either MIN_EXPR or MAX_EXPR. */ @@ -1778,8 +1880,14 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref, if (const access_ref *cache_ref = qry->get_ref (ptr)) { /* If the pointer is in the cache set *PREF to what it refers - to and return success. */ + to and return success. + FIXME: BNDRNG is determined by each access and so it doesn't + belong in access_ref. Until the design is changed, keep it + unchanged here. */ + const offset_int bndrng[2] = { pref->bndrng[0], pref->bndrng[1] }; *pref = *cache_ref; + pref->bndrng[0] = bndrng[0]; + pref->bndrng[1] = bndrng[1]; return true; } } @@ -1928,9 +2036,13 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref, return true; } - if (code == ADDR_EXPR - || code == SSA_NAME) - return compute_objsize_r (rhs, ostype, pref, snlim, qry); + if (code == ADDR_EXPR || code == SSA_NAME) + { + if (!compute_objsize_r (rhs, ostype, pref, snlim, qry)) + return false; + qry->put_ref (ptr, *pref); + return true; + } /* (This could also be an assignment from a nonlocal pointer.) Save PTR to mention in diagnostics but otherwise treat it as a pointer diff --git a/gcc/pointer-query.h b/gcc/pointer-query.h index eb7e90dde03..3c8172c652d 100644 --- a/gcc/pointer-query.h +++ b/gcc/pointer-query.h @@ -186,6 +186,9 @@ public: /* Flush the cache. */ void flush_cache (); + /* Dump statistics and optionally cache contents to DUMP_FILE. */ + void dump (FILE *, bool = false); + /* A Ranger instance. May be null to use global ranges. */ range_query *rvals; /* Cache of SSA_NAMEs. May be null to disable caching. */ diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-73.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-73.c new file mode 100644 index 00000000000..0bb4afecc7e --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-73.c @@ -0,0 +1,35 @@ +/* + { dg-do compile } + { dg-options "-Wall" } */ + +typedef __SIZE_TYPE__ size_t; + +int memcmp (const void*, const void*, size_t); +int strncmp (const char*, const char*, size_t); +char* stpncpy (char*, const char*, size_t); +char* strncpy (char*, const char*, size_t); + +extern char a4[4], b5[5]; + +struct A { char a4[4]; }; + +extern volatile int i; +extern void* volatile ptr; + +void test_stpncpy (struct A *p) +{ + ptr = stpncpy (a4, b5, 4); + ptr = stpncpy (a4, b5, 5); // { dg-warning "writing 5 bytes" } + + ptr = stpncpy (p->a4, b5, 4); + ptr = stpncpy (p->a4, b5, 5); // { dg-warning "writing 5 bytes" } +} + +void test_strncpy (struct A *p) +{ + ptr = strncpy (a4, b5, 4); + ptr = strncpy (a4, b5, 5); // { dg-warning "writing 5 bytes" } + + ptr = strncpy (p->a4, b5, 4); + ptr = strncpy (p->a4, b5, 5); // { dg-warning "writing 5 bytes" } +} diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index 15391da8104..11d1712576d 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -5820,27 +5820,7 @@ printf_strlen_execute (function *fun, bool warn_only) walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun)); if (dump_file && (dump_flags & TDF_DETAILS)) - { - unsigned nused = 0; - unsigned nidxs = walker.ptr_qry.var_cache->indices.length (); - for (unsigned i = 0; i != nidxs; ++i) - if (walker.ptr_qry.var_cache->indices[i]) - ++nused; - - fprintf (dump_file, "pointer_query counters\n" - " index cache size: %u\n" - " utilization: %u%%\n" - " access cache size: %u\n" - " hits: %u\n" - " misses: %u\n" - " failures: %u\n" - " max_depth: %u\n", - nidxs, - nidxs == 0 ? 0 : (nused * 100) / nidxs, - walker.ptr_qry.var_cache->access_refs.length (), - walker.ptr_qry.hits, walker.ptr_qry.misses, - walker.ptr_qry.failures, walker.ptr_qry.max_depth); - } + walker.ptr_qry.dump (dump_file); ssa_ver_to_stridx.release (); strinfo_pool.release ();