[PATCH] integrate sprintf pass into strlen (PR 83431)
Martin Sebor
msebor@gmail.com
Fri Jul 12 22:04:00 GMT 2019
...
>> So if I'm reading things correctly, it appears gimple-ssa-sprintf.c is
>> no longer a distinct pass. Instead it co-exists with the strlen pass.
>> Right?
>
> Yes. strlen just calls into sprintf to handle the calls.
>
>>> diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
>>> index a0934bcaf87..b05e4050f1d 100644
>>> --- a/gcc/gimple-ssa-sprintf.c
>>> +++ b/gcc/gimple-ssa-sprintf.c
>>> @@ -683,7 +618,7 @@ fmtresult::type_max_digits (tree type, int base)
>>> Â static bool
>>> Â get_int_range (tree, HOST_WIDE_INT *, HOST_WIDE_INT *, bool,
>>> HOST_WIDE_INT,
>>> -Â Â Â Â Â Â Â Â Â Â class vr_values *vr_values);
>>> +Â Â Â Â Â Â Â Â Â Â const class vr_values *vr_values);
>> FWIW, I think this is something *I* could do a lot better at.
>> Specifically I think we're not supposed to be writing the "class" here
>> and I'm not as good as I should be with marking things const. Thanks
>> for cleaning up my lack of consts.
>
> I think you did the best you could given the APIs you had to work
> with There's still plenty of room to improve const-correctness but
> it involves changing other APIs outsid strlen/sprintf.
>
>>> diff --git a/gcc/passes.def b/gcc/passes.def
>>> index 9a5b0cd554a..637e228f988 100644
>>> --- a/gcc/passes.def
>>> +++ b/gcc/passes.def
>>> @@ -42,7 +42,7 @@ along with GCC; see the file COPYING3. If not see
>>> Â Â Â NEXT_PASS (pass_build_cfg);
>>> Â Â Â NEXT_PASS (pass_warn_function_return);
>>> Â Â Â NEXT_PASS (pass_expand_omp);
>>> -Â NEXT_PASS (pass_sprintf_length, false);
>>> +Â NEXT_PASS (pass_strlen, false);
>> So this is something we discussed a bit on the phone. This is very
>> early in the pipeline -- before we've gone into SSA form.
>>
>> I'm a bit concerned that we're running strlen that early without some
>> kind of auditing of whether or not the strlen pass can safely run that
>> early. Similarly have we done any review for issues that might arise
>> from running strlen more than once? I guess in some small way
>> encapsulating the state into a class like my unsubmitted patch does
>> would help there.
>
> The strlen optimization machinery only runs once. The code avoids
> running it when the pass is invoked early and only calls into sprintf
> to do format checking.
>
>>
>> More generally I think we concluded that the placement of sprintf this
>> early was driven by the early placement of walloca. I don't want to
>> open a huge can of worms here, but do we really need to run this early
>> in the pipeline?
>
> We decided to run it early when optimization is disabled because
> there's a good amount of code that can be checked even without
> ranges and string lengths (especially at the conservative level
> 2 setting when we consider the largest integers and array sizes
> instead of values or string lengths).
>
> For example, this is diagnosed for the potential buffer overflow
> at -Wformat-overflow=2 even without optimization:
>
> Â char a[8], s[4];
>
> Â void f (int i)
> Â {
> Â Â Â __builtin_sprintf (a, "%s = %i", s, i);
> Â }
>
> Â warning: â%iâ directive writing between 1 and 11 bytes into a region
> of size between 2 and 5 [-Wformat-overflow=]
>
>
>>> diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
>>> index 74cd6c44874..89932713476 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"
>> Nit: Drop the extra newline.
>>
>>> @@ -604,14 +614,19 @@ 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 constant string length, or NULL if it can't be
>>> computed. */>
>>> Â 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;
>> IIUC get_string_length could return a non-constant expression that gives
>> the runtime length of a string.  So I think the comment change is a bit
>> misleading.
>
> Yes, good catch, thanks!
>
>> Nit: Use NULL rather than null. I think this happens in more than one
>> place in your patch. Similarly I think we generally use NUL rather than
>> nul when referring to a single character.
> The term is a "null pointer."Â NULL is a C macro that has in C++ 11
> been superseded by nullptr. I don't mind using NUL character but
> I also don't think it matters. No one will be confused about what
> either means.
>
>>> +/* 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. */
>>> +
>>> +static bool
>>> +get_range_strlen_dynamic (tree src, c_strlen_data *pdata, bitmap
>>> *visited,
>>> +Â Â Â Â Â Â Â Â Â Â Â Â Â const vr_values *rvals)
>> [ ... ]
>> We've already touched on the need to limit the backwards walk out of
>> this code. Limiting based on the product of # phi args * number of phis
>> visited, recursion depth, or number of SSA_NAME_DEF_STMTs visited all
>> seem reasonable to me.
>>
>> I do think Richi's suggestion for figuring out a suitable inflection
>> point is reasonable.
>
> It's easy enough to add here. But I know I've introduced other
> algorithms that recurse on SSA_NAME_DEF_STMT, and I'm quite sure
> others predate those. To make a difference I think we need to
> review at least the one most likely to be exposed to this problem
> and introduce the same limit there. I could probably fix the ones
> I wrote reasonably quickly, but making the change to the others
> would be much more of a project. I looked to see how pervasive
> this might be and here is just a small sampling of things that
> jumped out at me in a quick grep search:
>
> Â *Â compute_builtin_object_size (for _FORTIFY_SOURCE)
> Â *Â compute_objsize (for -Wstringop-overflow)
> Â *Â get_range_strlen
> Â *Â maybe_fold_{and,or}_comparisons in gimple-fold.c
> Â *Â -Warray-bounds (computing an offset into an object)
> Â *Â -Wrestrict (computing an offset into an object)
> Â *Â -Wreturn-local-addr (is_addr_local)
> Â *Â -Wuninitialized (collect_phi_def_edges)
>
> Given how wide-spread this technique seems to be, if the recursion
> is in fact a problem it's just as likely (if not more) to come up
> in the folder or in BOS or some other place as it is here. So if
> it needs fixing it seems it should be done as its own project and
> everywhere (or as close as we can get), and not as part of this
> integration.
>
>> Â +Â if (strinfo *si = get_strinfo (idx))
>>> +Â Â Â {
>>> +Â Â Â Â Â pdata->minlen = get_string_length (si);
>>> +Â Â Â Â Â if (!pdata->minlen
>>> +Â Â Â Â Â && si->nonzero_chars)
>> Nit: No need for a line break in that conditional.
>>
>>
>>
>>> +
>>> +/* 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 ARG, or the size of the largest array ARG 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)
>> I think you need to s/ARG/SRC/ in the function comment since SRC is the
>> name of the parameter.
>
> Yes, thanks.
>
>>
>>> @@ -3703,84 +4031,231 @@ fold_strstr_to_strncmp (tree rhs1, tree
>>> rhs2, gimple *stmt)
>>> Â Â Â Â Â }
>>> Â }
>>> +/* 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
>>> +check_and_optimize_call (gimple_stmt_iterator *gsi, const vr_values
>>> *rvals)
>> It was suggested that perhaps we should prefix this call name, but I
>> think the better solution might be to classify the pass and make this a
>> member function. That would seem to naturally fall to me since I've got
>> a classification patch for this code from last year that I could easily
>> update after your patch.
>
> Well, sure. The whole pass can be a class (or a set of classes).
> It began as C code and then C++ started to slowly and organically
> creep in. There are many other nice improvements we could make
> by putting C++ to better use. One very simple one I'd like is
> moving local variable declarations to the point of their
> initialization. Making the APIs const-correct would also improve
> readability. But I've resisted making these changes because I
> know people are sensitive to too much churn. If you think it's
> a good idea for me to make these changes let me know. I'd be
> happy to do it, just separately from this integration.
>
>>> +{
>>> +Â 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;
>>> +Â Â Â }
>> Shouldn't the guard here be similar, if not the same as the gate for the
>> old sprintf pass? Which was:
>>
>>> bool
>>> -pass_sprintf_length::gate (function *)
>>> -{
>>> -Â /* Run the pass iff -Warn-format-overflow or -Warn-format-truncation
>>> -Â Â Â Â is specified and either not optimizing and the pass is being
>>> invoked
>>> -Â Â Â Â early, or when optimizing and the pass is being invoked during
>>> -    optimization (i.e., "late"). */
>>> -Â return ((warn_format_overflow > 0
>>> -Â Â Â Â Â Â || warn_format_trunc > 0
>>> -Â Â Â Â Â Â || flag_printf_return_value)
>>> -Â Â Â Â Â && (optimize > 0) == fold_return_value);
>>> -}
>
> This test is now integrated into pass_strlen::gate so the guard
> above is only entered when the pass is running early (i.e., at
> -O0) or when the strlen optimization is disabled. Otherwise
> there's another call to handle_printf_call in the big switch
> statement in check_and_optimize_call.
>
>>> @@ -4119,7 +4504,10 @@ const pass_data pass_data_strlen =
>>> Â Â Â "strlen", /* name */
>>> Â Â Â OPTGROUP_NONE, /* optinfo_flags */
>>> Â Â Â TV_TREE_STRLEN, /* tv_id */
>>> -Â ( PROP_cfg | PROP_ssa ), /* properties_required */
>>> +Â /* Normally the pass would require PROP_ssa but because it also
>>> +Â Â Â Â 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 */
>>> @@ -4128,20 +4516,50 @@ const pass_data pass_data_strlen =
>> So the question I'd come back to is what are we capturing with the
>> instance that runs before we're in SSA form and can we reasonably catch
>> that stuff after going into SSA form?
>>
>> It may be that we went through this at the initial submission of the
>> sprintf patches. I simply can't remember.
>
> Please see my answer + example above.
>
>>> Â Â Â /* opt_pass methods: */
>>> -Â virtual bool gate (function *) { return flag_optimize_strlen != 0; }
>>> +
>>> +Â opt_pass * clone () {
>>> +Â Â Â return new pass_strlen (m_ctxt);
>>> +Â }
>> Nit. I think this is trivial enough to just have on a single line and
>> is generally consistent with other passes.
>>
>>
>>> +
>>> +Â virtual bool gate (function *);
>>> Â Â Â virtual unsigned int execute (function *);
>>> +Â void set_pass_param (unsigned int n, bool param)
>>> +Â Â Â {
>>> +Â Â Â Â Â gcc_assert (n == 0);
>>> +Â Â Â Â Â do_optimize = param;
>>> +Â Â Â }
>>> Â }; // class pass_strlen
>>> +
>>> +bool
>>> +pass_strlen::gate (function *)
>>> +{
>>> +Â /* Run the pass iff -Warn-format-overflow or -Warn-format-truncation
>> Aren't these Wformat-overflow and Wformat-trunction?
>
> Yes, thanks.
>
>>
>>
>>> +Â Â Â Â is specified and either not optimizing and the pass is being
>>> +Â Â Â Â invoked early, or when optimizing and the pass is being invoked
>>> +    during optimization (i.e., "late"). */
>>> +Â return ((warn_format_overflow > 0
>>> +Â Â Â Â Â Â || warn_format_trunc > 0
>>> +Â Â Â Â Â Â || flag_optimize_strlen > 0
>>> +Â Â Â Â Â Â || flag_printf_return_value)
>>> +Â Â Â Â Â && (optimize > 0) == do_optimize);
>>> +}
>> Ah, this is where the sprintf gateing clause went.
>>
>>
>>
>>> +
>>> Â unsigned int
>>> Â pass_strlen::execute (function *fun)
>>> Â {
>>> +Â strlen_optimize = do_optimize;
>>> +
>>> Â Â Â gcc_assert (!strlen_to_stridx);
>>> Â Â Â if (warn_stringop_overflow || warn_stringop_truncation)
>>> Â Â Â Â Â strlen_to_stridx = new hash_map<tree, stridx_strlenloc> ();
>>> @@ -4151,10 +4569,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 ();
>>> @@ -4175,6 +4600,15 @@ 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;
>>> Â }
>> Is scev really that useful here? If it is, fine, if not I'd rather not
>> pay the price to set it up.
>
> It was introduced by Aldy to fix PR 85598.
>
>>
>> My brain is turning to mush, so I think I'm going to need to do another
>> iteration over this patch.
>
> I want to respond because it's been a while but I'll post an updated
> patch later this week. In the meantime, if you have more comments
> on the rest of it please send them my way.
Attached is the updated patch. I added the recursion limit to
get_range_strlen_dynamic (and tests for it) but not to
get_range_strlen in gimple-fold.c or any of the other functions
outside tree-ssa-strlen.c. I will do that separately, once this
work has been committed.
Rested on x86_64-linux.
Martin
-------------- next part --------------
A non-text attachment was scrubbed...
Name: gcc-83431.diff
Type: text/x-patch
Size: 141709 bytes
Desc: not available
URL: <http://gcc.gnu.org/pipermail/gcc-patches/attachments/20190712/2604ae8e/attachment.bin>
More information about the Gcc-patches
mailing list