diff options
author | Martin Sebor <msebor@redhat.com> | 2019-08-26 18:29:45 +0000 |
---|---|---|
committer | Martin Sebor <msebor@gcc.gnu.org> | 2019-08-26 12:29:45 -0600 |
commit | 22fca489eaf98f2691772b51773a1e4eb7bb4ef2 (patch) | |
tree | 268157065335369b092a662c434f7753bb19eb7e /gcc/tree-ssa-strlen.c | |
parent | 59bce4ad03e438e81dcaa8ed956fbad6461c7d75 (diff) | |
download | gcc-22fca489eaf98f2691772b51773a1e4eb7bb4ef2.zip gcc-22fca489eaf98f2691772b51773a1e4eb7bb4ef2.tar.gz gcc-22fca489eaf98f2691772b51773a1e4eb7bb4ef2.tar.bz2 |
PR tree-optimization/83431 - -Wformat-truncation may incorrectly report truncation
gcc/ChangeLog:
PR c++/83431
* gimple-ssa-sprintf.c (pass_data_sprintf_length): Remove object.
(sprintf_dom_walker): Remove class.
(get_int_range): Make argument const.
(directive::fmtfunc, directive::set_precision): Same.
(format_none): Same.
(build_intmax_type_nodes): Same.
(adjust_range_for_overflow): Same.
(format_floating): Same.
(format_character): Same.
(format_string): Same.
(format_plain): Same.
(get_int_range): Cast away constness.
(format_integer): Same.
(get_string_length): Call get_range_strlen_dynamic. Handle
null lendata.maxbound.
(should_warn_p): Adjust argument scope qualifier.
(maybe_warn): Same.
(format_directive): Same.
(parse_directive): Same.
(is_call_safe): Same.
(try_substitute_return_value): Same.
(sprintf_dom_walker::handle_printf_call): Rename...
(handle_printf_call): ...to this. Initialize target to host charmap
here instead of in pass_sprintf_length::execute.
(struct call_info): Make global.
(sprintf_dom_walker::compute_format_length): Make global.
(sprintf_dom_walker::handle_gimple_call): Same.
* passes.def (pass_sprintf_length): Replace with pass_strlen.
* print-rtl.c (print_pattern): Reduce the number of spaces to
avoid -Wformat-truncation.
* tree-pass.h (make_pass_warn_printf): New function.
* tree-ssa-strlen.c (strlen_optimize): New variable.
(get_string_length): Add comments.
(get_range_strlen_dynamic): New function.
(check_and_optimize_call): New function.
(handle_integral_assign): New function.
(strlen_check_and_optimize_stmt): Factor code out into
strlen_check_and_optimize_call and handle_integral_assign.
(strlen_dom_walker::evrp): New member.
(strlen_dom_walker::before_dom_children): Use evrp member.
(strlen_dom_walker::after_dom_children): Use evrp member.
(printf_strlen_execute): New function.
(pass_strlen::gate): Update to handle printf calls.
(dump_strlen_info): New function.
(pass_data_warn_printf): New variable.
(pass_warn_printf): New class.
* tree-ssa-strlen.h (get_range_strlen_dynamic): Declare.
(handle_printf_call): Same.
* tree-vrp.c (value_range_base::type): Adjust assertion.
* vr-values.c (vr_values::update_value_range): Use type of the first
argument rather than the second.
gcc/testsuite/ChangeLog:
PR c++/83431
* gcc.dg/strlenopt-63.c: New test.
* gcc.dg/pr79538.c: Adjust text of expected warning.
* gcc.dg/pr81292-1.c: Adjust pass name.
* gcc.dg/pr81292-2.c: Same.
* gcc.dg/pr81703.c: Same.
* gcc.dg/strcmpopt_2.c: Same.
* gcc.dg/strcmpopt_3.c: Same.
* gcc.dg/strcmpopt_4.c: Same.
* gcc.dg/strlenopt-1.c: Same.
* gcc.dg/strlenopt-10.c: Same.
* gcc.dg/strlenopt-11.c: Same.
* gcc.dg/strlenopt-13.c: Same.
* gcc.dg/strlenopt-14g.c: Same.
* gcc.dg/strlenopt-14gf.c: Same.
* gcc.dg/strlenopt-15.c: Same.
* gcc.dg/strlenopt-16g.c: Same.
* gcc.dg/strlenopt-17g.c: Same.
* gcc.dg/strlenopt-18g.c: Same.
* gcc.dg/strlenopt-19.c: Same.
* gcc.dg/strlenopt-1f.c: Same.
* gcc.dg/strlenopt-2.c: Same.
* gcc.dg/strlenopt-20.c: Same.
* gcc.dg/strlenopt-21.c: Same.
* gcc.dg/strlenopt-22.c: Same.
* gcc.dg/strlenopt-22g.c: Same.
* gcc.dg/strlenopt-24.c: Same.
* gcc.dg/strlenopt-25.c: Same.
* gcc.dg/strlenopt-26.c: Same.
* gcc.dg/strlenopt-27.c: Same.
* gcc.dg/strlenopt-28.c: Same.
* gcc.dg/strlenopt-29.c: Same.
* gcc.dg/strlenopt-2f.c: Same.
* gcc.dg/strlenopt-3.c: Same.
* gcc.dg/strlenopt-30.c: Same.
* gcc.dg/strlenopt-31g.c: Same.
* gcc.dg/strlenopt-32.c: Same.
* gcc.dg/strlenopt-33.c: Same.
* gcc.dg/strlenopt-33g.c: Same.
* gcc.dg/strlenopt-34.c: Same.
* gcc.dg/strlenopt-35.c: Same.
* gcc.dg/strlenopt-4.c: Same.
* gcc.dg/strlenopt-48.c: Same.
* gcc.dg/strlenopt-49.c: Same.
* gcc.dg/strlenopt-4g.c: Same.
* gcc.dg/strlenopt-4gf.c: Same.
* gcc.dg/strlenopt-5.c: Same.
* gcc.dg/strlenopt-50.c: Same.
* gcc.dg/strlenopt-51.c: Same.
* gcc.dg/strlenopt-52.c: Same.
* gcc.dg/strlenopt-53.c: Same.
* gcc.dg/strlenopt-54.c: Same.
* gcc.dg/strlenopt-55.c: Same.
* gcc.dg/strlenopt-56.c: Same.
* gcc.dg/strlenopt-6.c: Same.
* gcc.dg/strlenopt-61.c: Same.
* gcc.dg/strlenopt-7.c: Same.
* gcc.dg/strlenopt-8.c: Same.
* gcc.dg/strlenopt-9.c: Same.
* gcc.dg/strlenopt.h (snprintf, snprintf): Declare.
* gcc.dg/tree-ssa/builtin-snprintf-6.c: New test.
* gcc.dg/tree-ssa/builtin-snprintf-7.c: New test.
* gcc.dg/tree-ssa/builtin-snprintf-8.c: New test.
* gcc.dg/tree-ssa/builtin-snprintf-9.c: New test.
* gcc.dg/tree-ssa/builtin-sprintf-warn-21.c: New test.
* gcc.dg/tree-ssa/dump-4.c: New test.
* gcc.dg/tree-ssa/pr83501.c: Adjust pass name.
From-SVN: r274933
Diffstat (limited to 'gcc/tree-ssa-strlen.c')
-rw-r--r-- | gcc/tree-ssa-strlen.c | 905 |
1 files changed, 697 insertions, 208 deletions
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index ef2b6ae..5c5b838 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -55,6 +55,12 @@ along with GCC; see the file COPYING3. If not see #include "intl.h" #include "attribs.h" #include "calls.h" +#include "cfgloop.h" +#include "tree-ssa-loop.h" +#include "tree-scalar-evolution.h" + +#include "vr-values.h" +#include "gimple-ssa-evrp-analyze.h" /* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value is an index into strinfo vector, negative value stands for @@ -64,6 +70,9 @@ static vec<int> ssa_ver_to_stridx; /* Number of currently active string indexes plus one. */ static int max_stridx; +/* Set to true to optimize, false when just checking. */ +static bool strlen_optimize; + /* String information record. */ struct strinfo { @@ -154,7 +163,8 @@ struct decl_stridxlist_map /* Hash table for mapping decls to a chained list of offset -> idx mappings. */ -static hash_map<tree_decl_hash, stridxlist> *decl_to_stridxlist_htab; +typedef hash_map<tree_decl_hash, stridxlist> decl_to_stridxlist_htab_t; +static decl_to_stridxlist_htab_t *decl_to_stridxlist_htab; /* Hash table mapping strlen (or strnlen with constant bound and return smaller than bound) calls to stridx instances describing @@ -604,14 +614,21 @@ set_endptr_and_length (location_t loc, strinfo *si, tree endptr) si->full_string_p = true; } -/* Return string length, or NULL if it can't be computed. */ +/* Return the string length, or NULL if it can't be computed. + The length may but need not be constant. Instead, it might be + the result of a strlen() call. */ static tree get_string_length (strinfo *si) { + /* If the length has already been computed return it if it's exact + (i.e., the string is nul-terminated at NONZERO_CHARS), or return + null if it isn't. */ if (si->nonzero_chars) return si->full_string_p ? si->nonzero_chars : NULL; + /* If the string is the result of one of the built-in calls below + attempt to compute the length from the call statement. */ if (si->stmt) { gimple *stmt = si->stmt, *lenstmt; @@ -702,6 +719,336 @@ get_string_length (strinfo *si) return si->nonzero_chars; } +/* Dump strlen data to FP for statement STMT. When non-null, RVALS + points to EVRP info and is used to dump strlen range for non-constant + results. */ + +DEBUG_FUNCTION void +dump_strlen_info (FILE *fp, gimple *stmt, const vr_values *rvals) +{ + if (stmt) + { + fprintf (fp, "\nDumping strlen pass data after "); + print_gimple_expr (fp, stmt, TDF_LINENO); + fputc ('\n', fp); + } + else + fprintf (fp, "\nDumping strlen pass data\n"); + + fprintf (fp, "max_stridx = %i\n", max_stridx); + fprintf (fp, "ssa_ver_to_stridx has %u elements\n", + ssa_ver_to_stridx.length ()); + fprintf (fp, "stridx_to_strinfo"); + if (stridx_to_strinfo) + { + fprintf (fp, " has %u elements\n", stridx_to_strinfo->length ()); + for (unsigned i = 0; i != stridx_to_strinfo->length (); ++i) + { + if (strinfo *si = (*stridx_to_strinfo)[i]) + { + if (!si->idx) + continue; + fprintf (fp, " idx = %i", si->idx); + if (si->ptr) + { + fprintf (fp, ", ptr = "); + print_generic_expr (fp, si->ptr); + } + fprintf (fp, ", nonzero_chars = "); + print_generic_expr (fp, si->nonzero_chars); + if (TREE_CODE (si->nonzero_chars) == SSA_NAME) + { + value_range_kind rng = VR_UNDEFINED; + wide_int min, max; + if (rvals) + { + const value_range *vr + = CONST_CAST (class vr_values *, rvals) + ->get_value_range (si->nonzero_chars); + rng = vr->kind (); + if (range_int_cst_p (vr)) + { + min = wi::to_wide (vr->min ()); + max = wi::to_wide (vr->max ()); + } + else + rng = VR_UNDEFINED; + } + else + rng = get_range_info (si->nonzero_chars, &min, &max); + + if (rng == VR_RANGE || rng == VR_ANTI_RANGE) + { + fprintf (fp, " %s[%llu, %llu]", + rng == VR_RANGE ? "" : "~", + (long long) min.to_uhwi (), + (long long) max.to_uhwi ()); + } + } + fprintf (fp, " , refcount = %i", si->refcount); + if (si->stmt) + { + fprintf (fp, ", stmt = "); + print_gimple_expr (fp, si->stmt, 0); + } + if (si->writable) + fprintf (fp, ", writable"); + if (si->full_string_p) + fprintf (fp, ", full_string_p"); + if (strinfo *next = get_next_strinfo (si)) + { + fprintf (fp, ", {"); + do + fprintf (fp, "%i%s", next->idx, next->first ? ", " : ""); + while ((next = get_next_strinfo (next))); + fprintf (fp, "}"); + } + fputs ("\n", fp); + } + } + } + else + fprintf (fp, " = null\n"); + + fprintf (fp, "decl_to_stridxlist_htab"); + if (decl_to_stridxlist_htab) + { + fputs ("\n", fp); + typedef decl_to_stridxlist_htab_t::iterator iter_t; + for (iter_t it = decl_to_stridxlist_htab->begin (); + it != decl_to_stridxlist_htab->end (); ++it) + { + tree decl = (*it).first; + stridxlist *list = &(*it).second; + fprintf (fp, " decl = "); + print_generic_expr (fp, decl); + if (list) + { + fprintf (fp, ", offsets = {"); + for (; list; list = list->next) + fprintf (fp, "%lli%s", (long long) list->offset, + list->next ? ", " : ""); + fputs ("}", fp); + } + fputs ("\n", fp); + } + } + else + fprintf (fp, " = null\n"); + + if (laststmt.stmt) + { + fprintf (fp, "laststmt = "); + print_gimple_expr (fp, laststmt.stmt, 0); + fprintf (fp, ", len = "); + print_generic_expr (fp, laststmt.len); + fprintf (fp, ", stridx = %i\n", laststmt.stridx); + } +} + +/* Attempt to determine the length of the string SRC. On success, store + the length in *PDATA and return true. Otherwise, return false. + VISITED is a bitmap of visited PHI nodes. RVALS points to EVRP info + and PSSA_DEF_MAX to an SSA_NAME assignment limit used to prevent runaway + recursion. */ + +static bool +get_range_strlen_dynamic (tree src, c_strlen_data *pdata, bitmap *visited, + const vr_values *rvals, unsigned *pssa_def_max) +{ + int idx = get_stridx (src); + if (!idx) + { + if (TREE_CODE (src) == SSA_NAME) + { + gimple *def_stmt = SSA_NAME_DEF_STMT (src); + if (gimple_code (def_stmt) == GIMPLE_PHI) + { + if (!*visited) + *visited = BITMAP_ALLOC (NULL); + + if (!bitmap_set_bit (*visited, SSA_NAME_VERSION (src))) + return true; + + if (*pssa_def_max == 0) + return false; + + --*pssa_def_max; + + /* Iterate over the PHI arguments and determine the minimum + and maximum length/size of each and incorporate them into + the overall result. */ + gphi *phi = as_a <gphi *> (def_stmt); + for (unsigned i = 0; i != gimple_phi_num_args (phi); ++i) + { + tree arg = gimple_phi_arg_def (phi, i); + if (arg == gimple_phi_result (def_stmt)) + continue; + + c_strlen_data argdata = { }; + if (get_range_strlen_dynamic (arg, &argdata, visited, rvals, + pssa_def_max)) + { + /* Set the DECL of an unterminated array this argument + refers to if one hasn't been found yet. */ + if (!pdata->decl && argdata.decl) + pdata->decl = argdata.decl; + + if (!argdata.minlen + || (integer_zerop (argdata.minlen) + && integer_all_onesp (argdata.maxbound) + && integer_all_onesp (argdata.maxlen))) + { + /* Set the upper bound of the length to unbounded. */ + pdata->maxlen = build_all_ones_cst (size_type_node); + continue; + } + + /* Adjust the minimum and maximum length determined + so far and the upper bound on the array size. */ + if (!pdata->minlen + || tree_int_cst_lt (argdata.minlen, pdata->minlen)) + pdata->minlen = argdata.minlen; + if (!pdata->maxlen + || tree_int_cst_lt (pdata->maxlen, argdata.maxlen)) + pdata->maxlen = argdata.maxlen; + if (!pdata->maxbound + || (tree_int_cst_lt (pdata->maxbound, + argdata.maxbound) + && !integer_all_onesp (argdata.maxbound))) + pdata->maxbound = argdata.maxbound; + } + else + pdata->maxlen = build_all_ones_cst (size_type_node); + } + + return true; + } + } + + /* Return success regardless of the result and handle *PDATA + in the caller. */ + get_range_strlen (src, pdata, 1); + return true; + } + + if (idx < 0) + { + /* SRC is a string of constant length. */ + pdata->minlen = build_int_cst (size_type_node, ~idx); + pdata->maxlen = pdata->minlen; + pdata->maxbound = pdata->maxlen; + return true; + } + + if (strinfo *si = get_strinfo (idx)) + { + pdata->minlen = get_string_length (si); + if (!pdata->minlen + && si->nonzero_chars) + { + if (TREE_CODE (si->nonzero_chars) == INTEGER_CST) + pdata->minlen = si->nonzero_chars; + else if (TREE_CODE (si->nonzero_chars) == SSA_NAME) + { + const value_range *vr + = CONST_CAST (class vr_values *, rvals) + ->get_value_range (si->nonzero_chars); + if (vr->kind () == VR_RANGE + && range_int_cst_p (vr)) + { + pdata->minlen = vr->min (); + pdata->maxlen = vr->max (); + } + else + pdata->minlen = build_zero_cst (size_type_node); + } + else + pdata->minlen = build_zero_cst (size_type_node); + + tree base = si->ptr; + if (TREE_CODE (base) == ADDR_EXPR) + base = TREE_OPERAND (base, 0); + + HOST_WIDE_INT off; + poly_int64 poff; + base = get_addr_base_and_unit_offset (base, &poff); + if (base + && DECL_P (base) + && TREE_CODE (TREE_TYPE (base)) == ARRAY_TYPE + && TYPE_SIZE_UNIT (TREE_TYPE (base)) + && poff.is_constant (&off)) + { + tree basetype = TREE_TYPE (base); + tree size = TYPE_SIZE_UNIT (basetype); + ++off; /* Increment for the terminating nul. */ + pdata->maxlen = fold_build2 (MINUS_EXPR, size_type_node, size, + build_int_cst (size_type_node, off)); + pdata->maxbound = pdata->maxlen; + } + else + pdata->maxlen = build_all_ones_cst (size_type_node); + } + else if (TREE_CODE (pdata->minlen) == SSA_NAME) + { + const value_range *vr + = CONST_CAST (class vr_values *, rvals) + ->get_value_range (si->nonzero_chars); + if (vr->kind () == VR_RANGE + && range_int_cst_p (vr)) + { + pdata->minlen = vr->min (); + pdata->maxlen = vr->max (); + pdata->maxbound = pdata->maxlen; + } + else + { + pdata->minlen = build_zero_cst (size_type_node); + pdata->maxlen = build_all_ones_cst (size_type_node); + } + } + else + { + pdata->maxlen = pdata->minlen; + pdata->maxbound = pdata->minlen; + } + + return true; + } + + return false; +} + +/* Analogous to get_range_strlen but for dynamically created strings, + i.e., those created by calls to strcpy as opposed to just string + constants. + Try to obtain the range of the lengths of the string(s) referenced + by SRC, or the size of the largest array SRC refers to if the range + of lengths cannot be determined, and store all in *PDATA. RVALS + points to EVRP info. */ + +void +get_range_strlen_dynamic (tree src, c_strlen_data *pdata, + const vr_values *rvals) +{ + bitmap visited = NULL; + + unsigned limit = PARAM_VALUE (PARAM_SSA_NAME_DEF_CHAIN_LIMIT); + if (!get_range_strlen_dynamic (src, pdata, &visited, rvals, &limit)) + { + /* On failure extend the length range to an impossible maximum + (a valid MAXLEN must be less than PTRDIFF_MAX - 1). Other + members can stay unchanged regardless. */ + pdata->minlen = ssize_int (0); + pdata->maxlen = build_all_ones_cst (size_type_node); + } + else if (!pdata->minlen) + pdata->minlen = ssize_int (0); + + if (visited) + BITMAP_FREE (visited); +} + /* Invalidate string length information for strings whose length might change due to stores in stmt. */ @@ -4017,84 +4364,232 @@ is_char_type (tree type) && TYPE_PRECISION (type) == TYPE_PRECISION (char_type_node)); } +/* Check the built-in call at GSI for validity and optimize it. + Return true to let the caller advance *GSI to the statement + in the CFG and false otherwise. */ + +static bool +strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, + const vr_values *rvals) +{ + gimple *stmt = gsi_stmt (*gsi); + + if (!flag_optimize_strlen + || !strlen_optimize + || !valid_builtin_call (stmt)) + { + /* When not optimizing we must be checking printf calls which + we do even for user-defined functions when they are declared + with attribute format. */ + handle_printf_call (gsi, rvals); + return true; + } + + tree callee = gimple_call_fndecl (stmt); + switch (DECL_FUNCTION_CODE (callee)) + { + case BUILT_IN_STRLEN: + case BUILT_IN_STRNLEN: + handle_builtin_strlen (gsi); + break; + case BUILT_IN_STRCHR: + handle_builtin_strchr (gsi); + break; + case BUILT_IN_STRCPY: + case BUILT_IN_STRCPY_CHK: + case BUILT_IN_STPCPY: + case BUILT_IN_STPCPY_CHK: + handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi); + break; + + case BUILT_IN_STRNCAT: + case BUILT_IN_STRNCAT_CHK: + handle_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi); + break; + + case BUILT_IN_STPNCPY: + case BUILT_IN_STPNCPY_CHK: + case BUILT_IN_STRNCPY: + case BUILT_IN_STRNCPY_CHK: + handle_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi); + break; + + case BUILT_IN_MEMCPY: + case BUILT_IN_MEMCPY_CHK: + case BUILT_IN_MEMPCPY: + case BUILT_IN_MEMPCPY_CHK: + handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi); + break; + case BUILT_IN_STRCAT: + case BUILT_IN_STRCAT_CHK: + handle_builtin_strcat (DECL_FUNCTION_CODE (callee), gsi); + break; + case BUILT_IN_MALLOC: + case BUILT_IN_CALLOC: + handle_builtin_malloc (DECL_FUNCTION_CODE (callee), gsi); + break; + case BUILT_IN_MEMSET: + if (handle_builtin_memset (gsi)) + return false; + break; + case BUILT_IN_MEMCMP: + if (handle_builtin_memcmp (gsi)) + return false; + break; + case BUILT_IN_STRCMP: + case BUILT_IN_STRNCMP: + if (handle_builtin_string_cmp (gsi)) + return false; + break; + default: + handle_printf_call (gsi, rvals); + break; + } + + return true; +} + +/* Handle an assignment statement at *GSI to a LHS of integral type. + If GSI's basic block needs clean-up of EH, set *CLEANUP_EH to true. */ + +static void +handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh) +{ + gimple *stmt = gsi_stmt (*gsi); + tree lhs = gimple_assign_lhs (stmt); + tree lhs_type = TREE_TYPE (lhs); + + enum tree_code code = gimple_assign_rhs_code (stmt); + if (code == COND_EXPR) + { + tree cond = gimple_assign_rhs1 (stmt); + enum tree_code cond_code = TREE_CODE (cond); + + if (cond_code == EQ_EXPR || cond_code == NE_EXPR) + fold_strstr_to_strncmp (TREE_OPERAND (cond, 0), + TREE_OPERAND (cond, 1), stmt); + } + else if (code == EQ_EXPR || code == NE_EXPR) + fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt), + gimple_assign_rhs2 (stmt), stmt); + else if (gimple_assign_load_p (stmt) + && TREE_CODE (lhs_type) == INTEGER_TYPE + && TYPE_MODE (lhs_type) == TYPE_MODE (char_type_node) + && (TYPE_PRECISION (lhs_type) + == TYPE_PRECISION (char_type_node)) + && !gimple_has_volatile_ops (stmt)) + { + tree off = integer_zero_node; + unsigned HOST_WIDE_INT coff = 0; + int idx = 0; + tree rhs1 = gimple_assign_rhs1 (stmt); + if (code == MEM_REF) + { + idx = get_stridx (TREE_OPERAND (rhs1, 0)); + if (idx > 0) + { + strinfo *si = get_strinfo (idx); + if (si + && si->nonzero_chars + && TREE_CODE (si->nonzero_chars) == INTEGER_CST + && (wi::to_widest (si->nonzero_chars) + >= wi::to_widest (off))) + off = TREE_OPERAND (rhs1, 1); + else + /* This case is not useful. See if get_addr_stridx + returns something usable. */ + idx = 0; + } + } + if (idx <= 0) + idx = get_addr_stridx (rhs1, NULL_TREE, &coff); + if (idx > 0) + { + strinfo *si = get_strinfo (idx); + if (si + && si->nonzero_chars + && TREE_CODE (si->nonzero_chars) == INTEGER_CST) + { + widest_int w1 = wi::to_widest (si->nonzero_chars); + widest_int w2 = wi::to_widest (off) + coff; + if (w1 == w2 + && si->full_string_p) + { + if (dump_file && (dump_flags & TDF_DETAILS) != 0) + { + fprintf (dump_file, "Optimizing: "); + print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); + } + + /* Reading the final '\0' character. */ + tree zero = build_int_cst (lhs_type, 0); + gimple_set_vuse (stmt, NULL_TREE); + gimple_assign_set_rhs_from_tree (gsi, zero); + *cleanup_eh + |= maybe_clean_or_replace_eh_stmt (stmt, + gsi_stmt (*gsi)); + stmt = gsi_stmt (*gsi); + update_stmt (stmt); + + if (dump_file && (dump_flags & TDF_DETAILS) != 0) + { + fprintf (dump_file, "into: "); + print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); + } + } + else if (w1 > w2) + { + /* Reading a character before the final '\0' + character. Just set the value range to ~[0, 0] + if we don't have anything better. */ + wide_int min, max; + signop sign = TYPE_SIGN (lhs_type); + int prec = TYPE_PRECISION (lhs_type); + value_range_kind vr = get_range_info (lhs, &min, &max); + if (vr == VR_VARYING + || (vr == VR_RANGE + && min == wi::min_value (prec, sign) + && max == wi::max_value (prec, sign))) + set_range_info (lhs, VR_ANTI_RANGE, + wi::zero (prec), wi::zero (prec)); + } + } + } + } + + if (strlen_to_stridx) + { + tree rhs1 = gimple_assign_rhs1 (stmt); + if (stridx_strlenloc *ps = strlen_to_stridx->get (rhs1)) + strlen_to_stridx->put (lhs, stridx_strlenloc (*ps)); + } +} + /* Attempt to check for validity of the performed access a single statement at *GSI using string length knowledge, and to optimize it. If the given basic block needs clean-up of EH, CLEANUP_EH is set to true. */ static bool -strlen_check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh) +check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh, + const vr_values *rvals) { gimple *stmt = gsi_stmt (*gsi); if (is_gimple_call (stmt)) { - tree callee = gimple_call_fndecl (stmt); - if (valid_builtin_call (stmt)) - switch (DECL_FUNCTION_CODE (callee)) - { - case BUILT_IN_STRLEN: - case BUILT_IN_STRNLEN: - handle_builtin_strlen (gsi); - break; - case BUILT_IN_STRCHR: - handle_builtin_strchr (gsi); - break; - case BUILT_IN_STRCPY: - case BUILT_IN_STRCPY_CHK: - case BUILT_IN_STPCPY: - case BUILT_IN_STPCPY_CHK: - handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi); - break; - - case BUILT_IN_STRNCAT: - case BUILT_IN_STRNCAT_CHK: - handle_builtin_strncat (DECL_FUNCTION_CODE (callee), gsi); - break; - - case BUILT_IN_STPNCPY: - case BUILT_IN_STPNCPY_CHK: - case BUILT_IN_STRNCPY: - case BUILT_IN_STRNCPY_CHK: - handle_builtin_stxncpy (DECL_FUNCTION_CODE (callee), gsi); - break; - - case BUILT_IN_MEMCPY: - case BUILT_IN_MEMCPY_CHK: - case BUILT_IN_MEMPCPY: - case BUILT_IN_MEMPCPY_CHK: - handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi); - break; - case BUILT_IN_STRCAT: - case BUILT_IN_STRCAT_CHK: - handle_builtin_strcat (DECL_FUNCTION_CODE (callee), gsi); - break; - case BUILT_IN_MALLOC: - case BUILT_IN_CALLOC: - handle_builtin_malloc (DECL_FUNCTION_CODE (callee), gsi); - break; - case BUILT_IN_MEMSET: - if (handle_builtin_memset (gsi)) - return false; - break; - case BUILT_IN_MEMCMP: - if (handle_builtin_memcmp (gsi)) - return false; - break; - case BUILT_IN_STRCMP: - case BUILT_IN_STRNCMP: - if (handle_builtin_string_cmp (gsi)) - return false; - break; - default: - break; - } + if (!strlen_check_and_optimize_call (gsi, rvals)) + return false; } + else if (!flag_optimize_strlen || !strlen_optimize) + return true; else if (is_gimple_assign (stmt) && !gimple_clobber_p (stmt)) { + /* Handle non-clobbering assignment. */ tree lhs = gimple_assign_lhs (stmt); + tree lhs_type = TREE_TYPE (lhs); - if (TREE_CODE (lhs) == SSA_NAME && POINTER_TYPE_P (TREE_TYPE (lhs))) + if (TREE_CODE (lhs) == SSA_NAME && POINTER_TYPE_P (lhs_type)) { if (gimple_assign_single_p (stmt) || (gimple_assign_cast_p (stmt) @@ -4106,117 +4601,10 @@ strlen_check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh) else if (gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR) handle_pointer_plus (gsi); } - else if (TREE_CODE (lhs) == SSA_NAME && INTEGRAL_TYPE_P (TREE_TYPE (lhs))) - { - enum tree_code code = gimple_assign_rhs_code (stmt); - if (code == COND_EXPR) - { - tree cond = gimple_assign_rhs1 (stmt); - enum tree_code cond_code = TREE_CODE (cond); - - if (cond_code == EQ_EXPR || cond_code == NE_EXPR) - fold_strstr_to_strncmp (TREE_OPERAND (cond, 0), - TREE_OPERAND (cond, 1), stmt); - } - else if (code == EQ_EXPR || code == NE_EXPR) - fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt), - gimple_assign_rhs2 (stmt), stmt); - else if (gimple_assign_load_p (stmt) - && TREE_CODE (TREE_TYPE (lhs)) == INTEGER_TYPE - && TYPE_MODE (TREE_TYPE (lhs)) == TYPE_MODE (char_type_node) - && (TYPE_PRECISION (TREE_TYPE (lhs)) - == TYPE_PRECISION (char_type_node)) - && !gimple_has_volatile_ops (stmt)) - { - tree off = integer_zero_node; - unsigned HOST_WIDE_INT coff = 0; - int idx = 0; - tree rhs1 = gimple_assign_rhs1 (stmt); - if (code == MEM_REF) - { - idx = get_stridx (TREE_OPERAND (rhs1, 0)); - if (idx > 0) - { - strinfo *si = get_strinfo (idx); - if (si - && si->nonzero_chars - && TREE_CODE (si->nonzero_chars) == INTEGER_CST - && (wi::to_widest (si->nonzero_chars) - >= wi::to_widest (off))) - off = TREE_OPERAND (rhs1, 1); - else - /* This case is not useful. See if get_addr_stridx - returns something usable. */ - idx = 0; - } - } - if (idx <= 0) - idx = get_addr_stridx (rhs1, NULL_TREE, &coff); - if (idx > 0) - { - strinfo *si = get_strinfo (idx); - if (si - && si->nonzero_chars - && TREE_CODE (si->nonzero_chars) == INTEGER_CST) - { - widest_int w1 = wi::to_widest (si->nonzero_chars); - widest_int w2 = wi::to_widest (off) + coff; - if (w1 == w2 - && si->full_string_p) - { - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "Optimizing: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - - /* Reading the final '\0' character. */ - tree zero = build_int_cst (TREE_TYPE (lhs), 0); - gimple_set_vuse (stmt, NULL_TREE); - gimple_assign_set_rhs_from_tree (gsi, zero); - *cleanup_eh - |= maybe_clean_or_replace_eh_stmt (stmt, - gsi_stmt (*gsi)); - stmt = gsi_stmt (*gsi); - update_stmt (stmt); - - if (dump_file && (dump_flags & TDF_DETAILS) != 0) - { - fprintf (dump_file, "into: "); - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); - } - } - else if (w1 > w2) - { - /* Reading a character before the final '\0' - character. Just set the value range to ~[0, 0] - if we don't have anything better. */ - wide_int min, max; - tree type = TREE_TYPE (lhs); - enum value_range_kind vr - = get_range_info (lhs, &min, &max); - if (vr == VR_VARYING - || (vr == VR_RANGE - && min == wi::min_value (TYPE_PRECISION (type), - TYPE_SIGN (type)) - && max == wi::max_value (TYPE_PRECISION (type), - TYPE_SIGN (type)))) - set_range_info (lhs, VR_ANTI_RANGE, - wi::zero (TYPE_PRECISION (type)), - wi::zero (TYPE_PRECISION (type))); - } - } - } - } - - if (strlen_to_stridx) - { - tree rhs1 = gimple_assign_rhs1 (stmt); - if (stridx_strlenloc *ps = strlen_to_stridx->get (rhs1)) - strlen_to_stridx->put (lhs, stridx_strlenloc (*ps)); - } - } - else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs)) + else if (TREE_CODE (lhs) == SSA_NAME && INTEGRAL_TYPE_P (lhs_type)) + /* Handle assignment to a character. */ + handle_integral_assign (gsi, cleanup_eh); + else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs)) { tree type = TREE_TYPE (lhs); if (TREE_CODE (type) == ARRAY_TYPE) @@ -4312,12 +4700,18 @@ class strlen_dom_walker : public dom_walker { public: strlen_dom_walker (cdi_direction direction) - : dom_walker (direction), m_cleanup_cfg (false) + : dom_walker (direction), + evrp (false), + m_cleanup_cfg (false) {} virtual edge before_dom_children (basic_block); virtual void after_dom_children (basic_block); + /* EVRP analyzer used for printf argument range processing, and + to track strlen results across integer variable assignments. */ + evrp_range_analyzer evrp; + /* Flag that will trigger TODO_cleanup_cfg to be returned in strlen execute function. */ bool m_cleanup_cfg; @@ -4329,6 +4723,8 @@ public: edge strlen_dom_walker::before_dom_children (basic_block bb) { + evrp.enter (bb); + basic_block dombb = get_immediate_dominator (CDI_DOMINATORS, bb); if (dombb == NULL) @@ -4402,8 +4798,16 @@ strlen_dom_walker::before_dom_children (basic_block bb) /* Attempt to optimize individual statements. */ for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi); ) - if (strlen_check_and_optimize_stmt (&gsi, &cleanup_eh)) - gsi_next (&gsi); + { + gimple *stmt = gsi_stmt (gsi); + + /* First record ranges generated by this statement so they + can be used by printf argument processing. */ + evrp.record_ranges_from_stmt (stmt, false); + + if (check_and_optimize_stmt (&gsi, &cleanup_eh, evrp.get_vr_values ())) + gsi_next (&gsi); + } if (cleanup_eh && gimple_purge_dead_eh_edges (bb)) m_cleanup_cfg = true; @@ -4420,6 +4824,8 @@ strlen_dom_walker::before_dom_children (basic_block bb) void strlen_dom_walker::after_dom_children (basic_block bb) { + evrp.leave (bb); + if (bb->aux) { stridx_to_strinfo = ((vec<strinfo *, va_heap, vl_embed> *) bb->aux); @@ -4437,39 +4843,13 @@ strlen_dom_walker::after_dom_children (basic_block bb) } } -/* Main entry point. */ - namespace { -const pass_data pass_data_strlen = -{ - GIMPLE_PASS, /* type */ - "strlen", /* name */ - OPTGROUP_NONE, /* optinfo_flags */ - TV_TREE_STRLEN, /* tv_id */ - ( PROP_cfg | PROP_ssa ), /* properties_required */ - 0, /* properties_provided */ - 0, /* properties_destroyed */ - 0, /* todo_flags_start */ - 0, /* todo_flags_finish */ -}; - -class pass_strlen : public gimple_opt_pass +static unsigned int +printf_strlen_execute (function *fun, bool warn_only) { -public: - pass_strlen (gcc::context *ctxt) - : gimple_opt_pass (pass_data_strlen, ctxt) - {} + strlen_optimize = !warn_only; - /* opt_pass methods: */ - virtual bool gate (function *) { return flag_optimize_strlen != 0; } - virtual unsigned int execute (function *); - -}; // class pass_strlen - -unsigned int -pass_strlen::execute (function *fun) -{ gcc_assert (!strlen_to_stridx); if (warn_stringop_overflow || warn_stringop_truncation) strlen_to_stridx = new hash_map<tree, stridx_strlenloc> (); @@ -4479,10 +4859,17 @@ pass_strlen::execute (function *fun) calculate_dominance_info (CDI_DOMINATORS); + bool use_scev = optimize > 0 && flag_printf_return_value; + if (use_scev) + { + loop_optimizer_init (LOOPS_NORMAL); + scev_initialize (); + } + /* String length optimization is implemented as a walk of the dominator tree and a forward walk of statements within each block. */ strlen_dom_walker walker (CDI_DOMINATORS); - walker.walk (fun->cfg->x_entry_block_ptr); + walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun)); ssa_ver_to_stridx.release (); strinfo_pool.release (); @@ -4503,12 +4890,114 @@ pass_strlen::execute (function *fun) strlen_to_stridx = NULL; } + if (use_scev) + { + scev_finalize (); + loop_optimizer_finalize (); + } + + /* Clean up object size info. */ + fini_object_sizes (); + return walker.m_cleanup_cfg ? TODO_cleanup_cfg : 0; } +/* This file defines two passes: one for warnings that runs only when + optimization is disabled, and another that implements optimizations + and also issues warnings. */ + +const pass_data pass_data_warn_printf = +{ + GIMPLE_PASS, /* type */ + "warn-printf", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_NONE, /* tv_id */ + /* Normally an optimization pass would require PROP_ssa but because + this pass runs early, with no optimization, to do sprintf format + checking, it only requires PROP_cfg. */ + PROP_cfg, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0, /* todo_flags_finish */ +}; + +class pass_warn_printf : public gimple_opt_pass +{ +public: + pass_warn_printf (gcc::context *ctxt) + : gimple_opt_pass (pass_data_warn_printf, ctxt) + {} + + virtual bool gate (function *); + virtual unsigned int execute (function *fun) + { + return printf_strlen_execute (fun, true); + } +}; + + +/* Return true to run the warning pass only when not optimizing and + iff either -Wformat-overflow or -Wformat-truncation is specified. */ + +bool +pass_warn_printf::gate (function *) +{ + return !optimize && (warn_format_overflow > 0 || warn_format_trunc > 0); +} + +const pass_data pass_data_strlen = +{ + GIMPLE_PASS, /* type */ + "strlen", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_TREE_STRLEN, /* tv_id */ + PROP_cfg | PROP_ssa, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0, /* todo_flags_finish */ +}; + +class pass_strlen : public gimple_opt_pass +{ +public: + pass_strlen (gcc::context *ctxt) + : gimple_opt_pass (pass_data_strlen, ctxt) + {} + + opt_pass * clone () { return new pass_strlen (m_ctxt); } + + virtual bool gate (function *); + virtual unsigned int execute (function *fun) + { + return printf_strlen_execute (fun, false); + } +}; + +/* Return true to run the pass only when the sprintf and/or strlen + optimizations are enabled and -Wformat-overflow or -Wformat-truncation + are specified. */ + +bool +pass_strlen::gate (function *) +{ + return ((warn_format_overflow > 0 + || warn_format_trunc > 0 + || flag_optimize_strlen > 0 + || flag_printf_return_value) + && optimize > 0); +} + } // anon namespace gimple_opt_pass * +make_pass_warn_printf (gcc::context *ctxt) +{ + return new pass_warn_printf (ctxt); +} + +gimple_opt_pass * make_pass_strlen (gcc::context *ctxt) { return new pass_strlen (ctxt); |