diff mbox series

integrate sprintf pass into strlen (PR 83431)

Message ID fda47188-e9f7-83b4-32a0-49f85daf28b2@gmail.com
State New
Headers show
Series integrate sprintf pass into strlen (PR 83431) | expand

Commit Message

Martin Sebor June 11, 2019, 11:26 p.m. UTC
The sprintf and strlen passes both work with strings but
run independently of one another and don't share state.  As
a result, lengths of strings dynamically created by functions
that are available to the strlen pass are not available to
sprintf.  Conversely, lengths of strings formatted by
the sprintf functions are not made available to the strlen
pass.  The result is less efficient code, poor diagnostics,
and ultimately less than optimal user experience.

The attached patch is the first step toward rectifying this
design problem.  It integrates the two passes into one and
exposes the string length data managed by the strlen pass to
the sprintf "module."  (It does not expose any sprintf data
to the strlen pass yet.)

The sprintf pass invocations in passes.def have been replaced
with those of strlen.  The first "early" invocation is only
effective for the sprintf module to enable warnings without
optimization.  The second invocation is "late" and enables
both warnings and the sprintf and strlen optimizations unless
explicitly disabled via -fno-optimize-strlen.

Since the strlen optimization is the second invocation of
the pass tests that scan the strlen dump have been adjusted
to look for the "strlen2" dump file.

The changes in the patch are mostly mechanical.  The one new
"feature" worth calling out is the get_range_strlen_dynamic
function.  It's analogous to get_range_strlen in gimple-fold.c
except that it makes use of the strlen "dynamically" obtained
string length info.  In cases when the info is not available
the former calls the latter.

The other new functions in tree-ssa-strlen.c are
check_and_optimize_call and handle_integral_assign: I added
them only to keep the check_and_optimize_stmt function from
getting excessively long and hard to follow.  Otherwise,
the code in the functions is unchanged.

There are a number of further enhancements to consider as
the next steps:
  *  enhance the strlen module to determine string length range
     information from integer variable to which it was previously
     assigned (necessary to fully resolve pr83431 and pr90625),
  *  make the sprintf/snprintf string length results directly
     available to the strlen module,
  *  enhance the sprintf module to optimize snprintf(0, 0, fmt)
     calls with fmt strings consisting solely of plain characters
     and %s directives to series of strlen calls on the arguments,
  *  and more.

Martin

Comments

Martin Sebor July 2, 2019, 1:47 a.m. UTC | #1
Attached is a more complete solution that fully resolves the bug
report by avoiding a warning in cases like:

   char a[32], b[8];

   void f (void)
   {
     if (strlen (a) < sizeof b - 2)
       snprintf (b, sizeof b, "b=%s", a); // no -Wformat-truncation
   }

It does that by having get_range_strlen_dynamic use the EVRP
information for non-constant strlen calls: EVRP has recorded
that the result is less sizeof b - 2 and so the function
returns this limited range of lengths to snprintf which can
then avoid warning.  It also improves the checking and can
find latent bugs it missed before (like the off-by-one in
print-rtl.c).

Besides improving the accuracy of the -Wformat-overflow and
truncation warnings this can also result in better code.
So far this only benefits snprintf but there may be other
opportunities to string functions as well (e.g., strcmp or
memcmp).

Jeff, I looked into your question/suggestion for me last week
when we spoke, to introduce some sort of a recursion limit for
get_range_strlen_dynamic.  It's easily doable but before we go
down that path I did some testing to see how bad it can get and
to compare it with get_range_strlen.  Here are the results for
a few packages.  The dept is the maximum recursion depth, success
and fail are the numbers of successful and failed calls to
the function:

   binutils-gdb:
                               depth   success     fail
     get_range_strlen:           319      8302    21621
     get_range_strlen_dynamic:    41      1503      161
   gcc:
     get_range_strlen:            46      7211    11365
     get_range_strlen_dynamic:    23     10272       12
   glibc:
     get_range_strlen:            76      2840    11422
     get_range_strlen_dynamic:    51      1186       46
   elfutils:
     get_range_strlen:            33      1198     2516
     get_range_strlen_dynamic:    31       685       36
   kernel:
     get_range_strlen:            25      5299    11698
     get_range_strlen_dynamic:    31      9911      122

Except the first case of get_range_strlen (I haven't looked into
it yet), it doesn't seem too bad, and with just one exception it's
better than get_range_strlen.  Let me know if you think it's worth
adding a parameter (I assume we'd use it for both functions) and
what to set it to.

On 6/11/19 5:26 PM, Martin Sebor wrote:
> The sprintf and strlen passes both work with strings but
> run independently of one another and don't share state.  As
> a result, lengths of strings dynamically created by functions
> that are available to the strlen pass are not available to
> sprintf.  Conversely, lengths of strings formatted by
> the sprintf functions are not made available to the strlen
> pass.  The result is less efficient code, poor diagnostics,
> and ultimately less than optimal user experience.
> 
> The attached patch is the first step toward rectifying this
> design problem.  It integrates the two passes into one and
> exposes the string length data managed by the strlen pass to
> the sprintf "module."  (It does not expose any sprintf data
> to the strlen pass yet.)
> 
> The sprintf pass invocations in passes.def have been replaced
> with those of strlen.  The first "early" invocation is only
> effective for the sprintf module to enable warnings without
> optimization.  The second invocation is "late" and enables
> both warnings and the sprintf and strlen optimizations unless
> explicitly disabled via -fno-optimize-strlen.
> 
> Since the strlen optimization is the second invocation of
> the pass tests that scan the strlen dump have been adjusted
> to look for the "strlen2" dump file.
> 
> The changes in the patch are mostly mechanical.  The one new
> "feature" worth calling out is the get_range_strlen_dynamic
> function.  It's analogous to get_range_strlen in gimple-fold.c
> except that it makes use of the strlen "dynamically" obtained
> string length info.  In cases when the info is not available
> the former calls the latter.
> 
> The other new functions in tree-ssa-strlen.c are
> check_and_optimize_call and handle_integral_assign: I added
> them only to keep the check_and_optimize_stmt function from
> getting excessively long and hard to follow.  Otherwise,
> the code in the functions is unchanged.
> 
> There are a number of further enhancements to consider as
> the next steps:
>   *  enhance the strlen module to determine string length range
>      information from integer variable to which it was previously
>      assigned (necessary to fully resolve pr83431 and pr90625),
>   *  make the sprintf/snprintf string length results directly
>      available to the strlen module,
>   *  enhance the sprintf module to optimize snprintf(0, 0, fmt)
>      calls with fmt strings consisting solely of plain characters
>      and %s directives to series of strlen calls on the arguments,
>   *  and more.
> 
> Martin
Richard Biener July 2, 2019, 9:59 a.m. UTC | #2
On Tue, Jul 2, 2019 at 3:48 AM Martin Sebor <msebor@gmail.com> wrote:
>
> Attached is a more complete solution that fully resolves the bug
> report by avoiding a warning in cases like:
>
>    char a[32], b[8];
>
>    void f (void)
>    {
>      if (strlen (a) < sizeof b - 2)
>        snprintf (b, sizeof b, "b=%s", a); // no -Wformat-truncation
>    }
>
> It does that by having get_range_strlen_dynamic use the EVRP
> information for non-constant strlen calls: EVRP has recorded
> that the result is less sizeof b - 2 and so the function
> returns this limited range of lengths to snprintf which can
> then avoid warning.  It also improves the checking and can
> find latent bugs it missed before (like the off-by-one in
> print-rtl.c).
>
> Besides improving the accuracy of the -Wformat-overflow and
> truncation warnings this can also result in better code.
> So far this only benefits snprintf but there may be other
> opportunities to string functions as well (e.g., strcmp or
> memcmp).
>
> Jeff, I looked into your question/suggestion for me last week
> when we spoke, to introduce some sort of a recursion limit for
> get_range_strlen_dynamic.  It's easily doable but before we go
> down that path I did some testing to see how bad it can get and
> to compare it with get_range_strlen.  Here are the results for
> a few packages.  The dept is the maximum recursion depth, success
> and fail are the numbers of successful and failed calls to
> the function:
>
>    binutils-gdb:
>                                depth   success     fail
>      get_range_strlen:           319      8302    21621
>      get_range_strlen_dynamic:    41      1503      161
>    gcc:
>      get_range_strlen:            46      7211    11365
>      get_range_strlen_dynamic:    23     10272       12
>    glibc:
>      get_range_strlen:            76      2840    11422
>      get_range_strlen_dynamic:    51      1186       46
>    elfutils:
>      get_range_strlen:            33      1198     2516
>      get_range_strlen_dynamic:    31       685       36
>    kernel:
>      get_range_strlen:            25      5299    11698
>      get_range_strlen_dynamic:    31      9911      122
>
> Except the first case of get_range_strlen (I haven't looked into
> it yet), it doesn't seem too bad, and with just one exception it's
> better than get_range_strlen.  Let me know if you think it's worth
> adding a parameter (I assume we'd use it for both functions) and
> what to set it to.

I think we want to avoid adding code to GCC that might turn out
quadratic for artificial testcases. History tells us that eventually
somebody will find a real-world testcase that hits it.  I would
suggest to not place a limit on the depth but on the number
of SSA_NAME_DEF_STMTs visited.  Not sure to what this
would turn out but I guess using a limit around 500 would work?
For the data above what's the biggest depth / highest number
of stmts we visit when we are able to compute a useful limit
and what is it when including failure?  The individual number
of successes/fails are not too interesting.  Maybe even provide
a histogram for the successful cases depth/stmt count?

Otherwise I like the merging - can you annotate the passes.def
entries with a comment indicating what the pass parameter
designates?  Like /* do optimize */?

Since we have a parallelization GSoC project running not
adding more global state to passes would be nice as well,
strlen has a lot of it that could be in a per invocation state
class.  Not a requirement for this patch though.

Besides the limiting no further comments from me - I'll leave
the details to Jeff.

Thanks a lot for doing this!
Richard.

>
> On 6/11/19 5:26 PM, Martin Sebor wrote:
> > The sprintf and strlen passes both work with strings but
> > run independently of one another and don't share state.  As
> > a result, lengths of strings dynamically created by functions
> > that are available to the strlen pass are not available to
> > sprintf.  Conversely, lengths of strings formatted by
> > the sprintf functions are not made available to the strlen
> > pass.  The result is less efficient code, poor diagnostics,
> > and ultimately less than optimal user experience.
> >
> > The attached patch is the first step toward rectifying this
> > design problem.  It integrates the two passes into one and
> > exposes the string length data managed by the strlen pass to
> > the sprintf "module."  (It does not expose any sprintf data
> > to the strlen pass yet.)
> >
> > The sprintf pass invocations in passes.def have been replaced
> > with those of strlen.  The first "early" invocation is only
> > effective for the sprintf module to enable warnings without
> > optimization.  The second invocation is "late" and enables
> > both warnings and the sprintf and strlen optimizations unless
> > explicitly disabled via -fno-optimize-strlen.
> >
> > Since the strlen optimization is the second invocation of
> > the pass tests that scan the strlen dump have been adjusted
> > to look for the "strlen2" dump file.
> >
> > The changes in the patch are mostly mechanical.  The one new
> > "feature" worth calling out is the get_range_strlen_dynamic
> > function.  It's analogous to get_range_strlen in gimple-fold.c
> > except that it makes use of the strlen "dynamically" obtained
> > string length info.  In cases when the info is not available
> > the former calls the latter.
> >
> > The other new functions in tree-ssa-strlen.c are
> > check_and_optimize_call and handle_integral_assign: I added
> > them only to keep the check_and_optimize_stmt function from
> > getting excessively long and hard to follow.  Otherwise,
> > the code in the functions is unchanged.
> >
> > There are a number of further enhancements to consider as
> > the next steps:
> >   *  enhance the strlen module to determine string length range
> >      information from integer variable to which it was previously
> >      assigned (necessary to fully resolve pr83431 and pr90625),
> >   *  make the sprintf/snprintf string length results directly
> >      available to the strlen module,
> >   *  enhance the sprintf module to optimize snprintf(0, 0, fmt)
> >      calls with fmt strings consisting solely of plain characters
> >      and %s directives to series of strlen calls on the arguments,
> >   *  and more.
> >
> > Martin
>
Jeff Law July 2, 2019, 7:53 p.m. UTC | #3
On 7/2/19 3:59 AM, Richard Biener wrote:
> On Tue, Jul 2, 2019 at 3:48 AM Martin Sebor <msebor@gmail.com> wrote:
>>
>> Attached is a more complete solution that fully resolves the bug
>> report by avoiding a warning in cases like:
>>
>>    char a[32], b[8];
>>
>>    void f (void)
>>    {
>>      if (strlen (a) < sizeof b - 2)
>>        snprintf (b, sizeof b, "b=%s", a); // no -Wformat-truncation
>>    }
>>
>> It does that by having get_range_strlen_dynamic use the EVRP
>> information for non-constant strlen calls: EVRP has recorded
>> that the result is less sizeof b - 2 and so the function
>> returns this limited range of lengths to snprintf which can
>> then avoid warning.  It also improves the checking and can
>> find latent bugs it missed before (like the off-by-one in
>> print-rtl.c).
>>
>> Besides improving the accuracy of the -Wformat-overflow and
>> truncation warnings this can also result in better code.
>> So far this only benefits snprintf but there may be other
>> opportunities to string functions as well (e.g., strcmp or
>> memcmp).
>>
>> Jeff, I looked into your question/suggestion for me last week
>> when we spoke, to introduce some sort of a recursion limit for
>> get_range_strlen_dynamic.  It's easily doable but before we go
>> down that path I did some testing to see how bad it can get and
>> to compare it with get_range_strlen.  Here are the results for
>> a few packages.  The dept is the maximum recursion depth, success
>> and fail are the numbers of successful and failed calls to
>> the function:
>>
>>    binutils-gdb:
>>                                depth   success     fail
>>      get_range_strlen:           319      8302    21621
>>      get_range_strlen_dynamic:    41      1503      161
>>    gcc:
>>      get_range_strlen:            46      7211    11365
>>      get_range_strlen_dynamic:    23     10272       12
>>    glibc:
>>      get_range_strlen:            76      2840    11422
>>      get_range_strlen_dynamic:    51      1186       46
>>    elfutils:
>>      get_range_strlen:            33      1198     2516
>>      get_range_strlen_dynamic:    31       685       36
>>    kernel:
>>      get_range_strlen:            25      5299    11698
>>      get_range_strlen_dynamic:    31      9911      122
>>
>> Except the first case of get_range_strlen (I haven't looked into
>> it yet), it doesn't seem too bad, and with just one exception it's
>> better than get_range_strlen.  Let me know if you think it's worth
>> adding a parameter (I assume we'd use it for both functions) and
>> what to set it to.
> 
> I think we want to avoid adding code to GCC that might turn out
> quadratic for artificial testcases. History tells us that eventually
> somebody will find a real-world testcase that hits it.  I would
> suggest to not place a limit on the depth but on the number
> of SSA_NAME_DEF_STMTs visited.  Not sure to what this
> would turn out but I guess using a limit around 500 would work?
> For the data above what's the biggest depth / highest number
> of stmts we visit when we are able to compute a useful limit
> and what is it when including failure?  The individual number
> of successes/fails are not too interesting.  Maybe even provide
> a histogram for the successful cases depth/stmt count?
Martin and I were kicking this around last week based on some feedback
from Jakub.  I agree that we want to avoid quadratic, even for
artificial testcases.  Whatever the solution finally is, we probably
want to do something very similar if not the same in the patch to detect
returning addresses in the local stack.

In an ideal world we'd have some kind of generic walker with the
limiter; I suspect there's more instances of this backwards walk lurking.

> 
> Since we have a parallelization GSoC project running not
> adding more global state to passes would be nice as well,
> strlen has a lot of it that could be in a per invocation state
> class.  Not a requirement for this patch though.
I have an independent patch which does some of this.  It's from about
this time last year and had to be put on the back burner.  It pulls
ssa_ver_to_stridx, max_stridx, strinfo_pool, stridx_to_strinfo,
strlenloc, strlen_to_stridx and stridx_obstack into the class along with
a lot of the support routines.

I guess it's time to resurrect that work :-)


jeff
Jeff Law July 2, 2019, 10:38 p.m. UTC | #4
On 7/1/19 7:47 PM, Martin Sebor wrote:
> Attached is a more complete solution that fully resolves the bug
> report by avoiding a warning in cases like:
> 
>   char a[32], b[8];
> 
>   void f (void)
>   {
>     if (strlen (a) < sizeof b - 2)
>       snprintf (b, sizeof b, "b=%s", a); // no -Wformat-truncation
>   }
> 
> It does that by having get_range_strlen_dynamic use the EVRP
> information for non-constant strlen calls: EVRP has recorded
> that the result is less sizeof b - 2 and so the function
> returns this limited range of lengths to snprintf which can
> then avoid warning.  It also improves the checking and can
> find latent bugs it missed before (like the off-by-one in
> print-rtl.c).
> 
> Besides improving the accuracy of the -Wformat-overflow and
> truncation warnings this can also result in better code.
> So far this only benefits snprintf but there may be other
> opportunities to string functions as well (e.g., strcmp or
> memcmp).
> 
> Jeff, I looked into your question/suggestion for me last week
> when we spoke, to introduce some sort of a recursion limit for
> get_range_strlen_dynamic.  It's easily doable but before we go
> down that path I did some testing to see how bad it can get and
> to compare it with get_range_strlen.  Here are the results for
> a few packages.  The dept is the maximum recursion depth, success
> and fail are the numbers of successful and failed calls to
> the function:
> 
>   binutils-gdb:
>                               depth   success     fail
>     get_range_strlen:           319      8302    21621
>     get_range_strlen_dynamic:    41      1503      161
>   gcc:
>     get_range_strlen:            46      7211    11365
>     get_range_strlen_dynamic:    23     10272       12
>   glibc:
>     get_range_strlen:            76      2840    11422
>     get_range_strlen_dynamic:    51      1186       46
>   elfutils:
>     get_range_strlen:            33      1198     2516
>     get_range_strlen_dynamic:    31       685       36
>   kernel:
>     get_range_strlen:            25      5299    11698
>     get_range_strlen_dynamic:    31      9911      122
> 
> Except the first case of get_range_strlen (I haven't looked into
> it yet), it doesn't seem too bad, and with just one exception it's
> better than get_range_strlen.  Let me know if you think it's worth
> adding a parameter (I assume we'd use it for both functions) and
> what to set it to.
> 
> On 6/11/19 5:26 PM, Martin Sebor wrote:
>> The sprintf and strlen passes both work with strings but
>> run independently of one another and don't share state.  As
>> a result, lengths of strings dynamically created by functions
>> that are available to the strlen pass are not available to
>> sprintf.  Conversely, lengths of strings formatted by
>> the sprintf functions are not made available to the strlen
>> pass.  The result is less efficient code, poor diagnostics,
>> and ultimately less than optimal user experience.
>>
>> The attached patch is the first step toward rectifying this
>> design problem.  It integrates the two passes into one and
>> exposes the string length data managed by the strlen pass to
>> the sprintf "module."  (It does not expose any sprintf data
>> to the strlen pass yet.)
>>
>> The sprintf pass invocations in passes.def have been replaced
>> with those of strlen.  The first "early" invocation is only
>> effective for the sprintf module to enable warnings without
>> optimization.  The second invocation is "late" and enables
>> both warnings and the sprintf and strlen optimizations unless
>> explicitly disabled via -fno-optimize-strlen.
>>
>> Since the strlen optimization is the second invocation of
>> the pass tests that scan the strlen dump have been adjusted
>> to look for the "strlen2" dump file.
>>
>> The changes in the patch are mostly mechanical.  The one new
>> "feature" worth calling out is the get_range_strlen_dynamic
>> function.  It's analogous to get_range_strlen in gimple-fold.c
>> except that it makes use of the strlen "dynamically" obtained
>> string length info.  In cases when the info is not available
>> the former calls the latter.
>>
>> The other new functions in tree-ssa-strlen.c are
>> check_and_optimize_call and handle_integral_assign: I added
>> them only to keep the check_and_optimize_stmt function from
>> getting excessively long and hard to follow.  Otherwise,
>> the code in the functions is unchanged.
>>
>> There are a number of further enhancements to consider as
>> the next steps:
>>   *  enhance the strlen module to determine string length range
>>      information from integer variable to which it was previously
>>      assigned (necessary to fully resolve pr83431 and pr90625),
>>   *  make the sprintf/snprintf string length results directly
>>      available to the strlen module,
>>   *  enhance the sprintf module to optimize snprintf(0, 0, fmt)
>>      calls with fmt strings consisting solely of plain characters
>>      and %s directives to series of strlen calls on the arguments,
>>   *  and more.
>>
>> Martin
> 
> 
> gcc-83431.diff
> 
> 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-ssa-strlen.c (strlen_optimize): New variable.
> 	(get_string_length): Add comments.
> 	(get_range_strlen_dynamic): New functions.
> 	(check_and_optimize_call): New function.
> 	(handle_integral_assign): New function.
> 	(strlen_check_and_optimize_stmt): Rename...
> 	(check_and_optimize_stmt): ...to this.  Factor code out into
> 	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.
> 	(pass_data_strlen): Remove property not satisfied during an early run.
> 	(pass_strlen::do_optimize): New data member.
> 	(pass_strlen::set_pass_param): New member function.
> 	(pass_strlen::gate): Update to handle printf calls.
> 	(pass_strlen::execute): Initialize loop and scev optimizers.
> 	(dump_strlen_info): New function.
> 	* tree-ssa-strlen.h (get_range_strlen_dynamic): Declare.
> 	(handle_printf_call): Same.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/83431
> 	* gcc.dg/strlenopt-63.c: New test.
> 	* 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-sprintf-warn-21.c: New test.
> 	* gcc.dg/tree-ssa/dump-4.c: New test.
> 	* gcc.dg/tree-ssa/pr83501.c: Adjust pass name.
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?


> 
> 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.


> 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.

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?

> 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.

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.



 +
> +/* 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.

 +  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.

> @@ -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.


> +{
> +  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);
> -}



> @@ -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.


>  
>    /* 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?


> +     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.

My brain is turning to mush, so I think I'm going to need to do another
iteration over this patch.


Jeff
Martin Sebor July 10, 2019, 9:51 p.m. UTC | #5
On 7/2/19 3:59 AM, Richard Biener wrote:
> On Tue, Jul 2, 2019 at 3:48 AM Martin Sebor <msebor@gmail.com> wrote:
>>
>> Attached is a more complete solution that fully resolves the bug
>> report by avoiding a warning in cases like:
>>
>>     char a[32], b[8];
>>
>>     void f (void)
>>     {
>>       if (strlen (a) < sizeof b - 2)
>>         snprintf (b, sizeof b, "b=%s", a); // no -Wformat-truncation
>>     }
>>
>> It does that by having get_range_strlen_dynamic use the EVRP
>> information for non-constant strlen calls: EVRP has recorded
>> that the result is less sizeof b - 2 and so the function
>> returns this limited range of lengths to snprintf which can
>> then avoid warning.  It also improves the checking and can
>> find latent bugs it missed before (like the off-by-one in
>> print-rtl.c).
>>
>> Besides improving the accuracy of the -Wformat-overflow and
>> truncation warnings this can also result in better code.
>> So far this only benefits snprintf but there may be other
>> opportunities to string functions as well (e.g., strcmp or
>> memcmp).
>>
>> Jeff, I looked into your question/suggestion for me last week
>> when we spoke, to introduce some sort of a recursion limit for
>> get_range_strlen_dynamic.  It's easily doable but before we go
>> down that path I did some testing to see how bad it can get and
>> to compare it with get_range_strlen.  Here are the results for
>> a few packages.  The dept is the maximum recursion depth, success
>> and fail are the numbers of successful and failed calls to
>> the function:
>>
>>     binutils-gdb:
>>                                 depth   success     fail
>>       get_range_strlen:           319      8302    21621
>>       get_range_strlen_dynamic:    41      1503      161
>>     gcc:
>>       get_range_strlen:            46      7211    11365
>>       get_range_strlen_dynamic:    23     10272       12
>>     glibc:
>>       get_range_strlen:            76      2840    11422
>>       get_range_strlen_dynamic:    51      1186       46
>>     elfutils:
>>       get_range_strlen:            33      1198     2516
>>       get_range_strlen_dynamic:    31       685       36
>>     kernel:
>>       get_range_strlen:            25      5299    11698
>>       get_range_strlen_dynamic:    31      9911      122
>>
>> Except the first case of get_range_strlen (I haven't looked into
>> it yet), it doesn't seem too bad, and with just one exception it's
>> better than get_range_strlen.  Let me know if you think it's worth
>> adding a parameter (I assume we'd use it for both functions) and
>> what to set it to.
> 
> I think we want to avoid adding code to GCC that might turn out
> quadratic for artificial testcases. History tells us that eventually
> somebody will find a real-world testcase that hits it.  I would
> suggest to not place a limit on the depth but on the number
> of SSA_NAME_DEF_STMTs visited.  Not sure to what this
> would turn out but I guess using a limit around 500 would work?

None of my builds comes even close that number so setting the limit
wouldn't have any adverse impact on any of them.  But there are
quite a number of other algorithms in GCC that recursively chase
SSE_NAME_DEF_STMTs, including in gimple-fold.c.  It would be
interesting to see data for those.

> For the data above what's the biggest depth / highest number
> of stmts we visit when we are able to compute a useful limit
> and what is it when including failure?  The individual number
> of successes/fails are not too interesting.  Maybe even provide
> a histogram for the successful cases depth/stmt count?

I changed the code to count SSA_NAMEs and PHI arguments (and do
other things).  The most I see for those are these in GDB/Binutils:

binutils/objcopy.c:5694:1: note: successful get_range_strlen 
(‘output_target’): depth:211, ssa_names:9, phi_args:207, 
result:{0,18446744073709551615,18446744073709551615}
binutils/objcopy.c:5694:1: note: successful get_range_strlen 
(‘input_target’): depth:104, ssa_names:4, phi_args:101, 
result:{0,18446744073709551615,18446744073709551615}
binutils/readelf.c:1152:1: note: successful get_range_strlen (‘rtype’): 
depth:189, ssa_names:73, phi_args:118, result:{10,18446744073709551615,25}
binutils/readelf.c:1152:1: note: successful get_range_strlen (‘rtype’): 
depth:201, ssa_names:77, phi_args:129, result:{10,18446744073709551615,25}

The first two work very hard only to find the string length and
array size are unbounded.  The latter two determine the string
is between 10 and 24 characters (i.e., it's stored in char[25]).

These are the only four with a three-digit depth.  There are
463 two-digit depths (ssa_names/phi args), and 9,642 single
digit ones in the GDB/Binutils build.

In GCC there are 92 two-digit depths and 17,448 single-digit.

Failures are usually very quick (with just one exception all
under 10 SSA_NAMEs and PHI args.)

> Otherwise I like the merging - can you annotate the passes.def
> entries with a comment indicating what the pass parameter
> designates?  Like /* do optimize */?

Sure.

> 
> Since we have a parallelization GSoC project running not
> adding more global state to passes would be nice as well,
> strlen has a lot of it that could be in a per invocation state
> class.  Not a requirement for this patch though.

It shouldn't be too hard to do in a followup patch.

Martin

> 
> Besides the limiting no further comments from me - I'll leave
> the details to Jeff.
> 
> Thanks a lot for doing this!
> Richard.
> 
>>
>> On 6/11/19 5:26 PM, Martin Sebor wrote:
>>> The sprintf and strlen passes both work with strings but
>>> run independently of one another and don't share state.  As
>>> a result, lengths of strings dynamically created by functions
>>> that are available to the strlen pass are not available to
>>> sprintf.  Conversely, lengths of strings formatted by
>>> the sprintf functions are not made available to the strlen
>>> pass.  The result is less efficient code, poor diagnostics,
>>> and ultimately less than optimal user experience.
>>>
>>> The attached patch is the first step toward rectifying this
>>> design problem.  It integrates the two passes into one and
>>> exposes the string length data managed by the strlen pass to
>>> the sprintf "module."  (It does not expose any sprintf data
>>> to the strlen pass yet.)
>>>
>>> The sprintf pass invocations in passes.def have been replaced
>>> with those of strlen.  The first "early" invocation is only
>>> effective for the sprintf module to enable warnings without
>>> optimization.  The second invocation is "late" and enables
>>> both warnings and the sprintf and strlen optimizations unless
>>> explicitly disabled via -fno-optimize-strlen.
>>>
>>> Since the strlen optimization is the second invocation of
>>> the pass tests that scan the strlen dump have been adjusted
>>> to look for the "strlen2" dump file.
>>>
>>> The changes in the patch are mostly mechanical.  The one new
>>> "feature" worth calling out is the get_range_strlen_dynamic
>>> function.  It's analogous to get_range_strlen in gimple-fold.c
>>> except that it makes use of the strlen "dynamically" obtained
>>> string length info.  In cases when the info is not available
>>> the former calls the latter.
>>>
>>> The other new functions in tree-ssa-strlen.c are
>>> check_and_optimize_call and handle_integral_assign: I added
>>> them only to keep the check_and_optimize_stmt function from
>>> getting excessively long and hard to follow.  Otherwise,
>>> the code in the functions is unchanged.
>>>
>>> There are a number of further enhancements to consider as
>>> the next steps:
>>>    *  enhance the strlen module to determine string length range
>>>       information from integer variable to which it was previously
>>>       assigned (necessary to fully resolve pr83431 and pr90625),
>>>    *  make the sprintf/snprintf string length results directly
>>>       available to the strlen module,
>>>    *  enhance the sprintf module to optimize snprintf(0, 0, fmt)
>>>       calls with fmt strings consisting solely of plain characters
>>>       and %s directives to series of strlen calls on the arguments,
>>>    *  and more.
>>>
>>> Martin
>>
Martin Sebor July 10, 2019, 11:54 p.m. UTC | #6
On 7/2/19 4:38 PM, Jeff Law wrote:
> On 7/1/19 7:47 PM, Martin Sebor wrote:
>> Attached is a more complete solution that fully resolves the bug
>> report by avoiding a warning in cases like:
>>
>>    char a[32], b[8];
>>
>>    void f (void)
>>    {
>>      if (strlen (a) < sizeof b - 2)
>>        snprintf (b, sizeof b, "b=%s", a); // no -Wformat-truncation
>>    }
>>
>> It does that by having get_range_strlen_dynamic use the EVRP
>> information for non-constant strlen calls: EVRP has recorded
>> that the result is less sizeof b - 2 and so the function
>> returns this limited range of lengths to snprintf which can
>> then avoid warning.  It also improves the checking and can
>> find latent bugs it missed before (like the off-by-one in
>> print-rtl.c).
>>
>> Besides improving the accuracy of the -Wformat-overflow and
>> truncation warnings this can also result in better code.
>> So far this only benefits snprintf but there may be other
>> opportunities to string functions as well (e.g., strcmp or
>> memcmp).
>>
>> Jeff, I looked into your question/suggestion for me last week
>> when we spoke, to introduce some sort of a recursion limit for
>> get_range_strlen_dynamic.  It's easily doable but before we go
>> down that path I did some testing to see how bad it can get and
>> to compare it with get_range_strlen.  Here are the results for
>> a few packages.  The dept is the maximum recursion depth, success
>> and fail are the numbers of successful and failed calls to
>> the function:
>>
>>    binutils-gdb:
>>                                depth   success     fail
>>      get_range_strlen:           319      8302    21621
>>      get_range_strlen_dynamic:    41      1503      161
>>    gcc:
>>      get_range_strlen:            46      7211    11365
>>      get_range_strlen_dynamic:    23     10272       12
>>    glibc:
>>      get_range_strlen:            76      2840    11422
>>      get_range_strlen_dynamic:    51      1186       46
>>    elfutils:
>>      get_range_strlen:            33      1198     2516
>>      get_range_strlen_dynamic:    31       685       36
>>    kernel:
>>      get_range_strlen:            25      5299    11698
>>      get_range_strlen_dynamic:    31      9911      122
>>
>> Except the first case of get_range_strlen (I haven't looked into
>> it yet), it doesn't seem too bad, and with just one exception it's
>> better than get_range_strlen.  Let me know if you think it's worth
>> adding a parameter (I assume we'd use it for both functions) and
>> what to set it to.
>>
>> On 6/11/19 5:26 PM, Martin Sebor wrote:
>>> The sprintf and strlen passes both work with strings but
>>> run independently of one another and don't share state.  As
>>> a result, lengths of strings dynamically created by functions
>>> that are available to the strlen pass are not available to
>>> sprintf.  Conversely, lengths of strings formatted by
>>> the sprintf functions are not made available to the strlen
>>> pass.  The result is less efficient code, poor diagnostics,
>>> and ultimately less than optimal user experience.
>>>
>>> The attached patch is the first step toward rectifying this
>>> design problem.  It integrates the two passes into one and
>>> exposes the string length data managed by the strlen pass to
>>> the sprintf "module."  (It does not expose any sprintf data
>>> to the strlen pass yet.)
>>>
>>> The sprintf pass invocations in passes.def have been replaced
>>> with those of strlen.  The first "early" invocation is only
>>> effective for the sprintf module to enable warnings without
>>> optimization.  The second invocation is "late" and enables
>>> both warnings and the sprintf and strlen optimizations unless
>>> explicitly disabled via -fno-optimize-strlen.
>>>
>>> Since the strlen optimization is the second invocation of
>>> the pass tests that scan the strlen dump have been adjusted
>>> to look for the "strlen2" dump file.
>>>
>>> The changes in the patch are mostly mechanical.  The one new
>>> "feature" worth calling out is the get_range_strlen_dynamic
>>> function.  It's analogous to get_range_strlen in gimple-fold.c
>>> except that it makes use of the strlen "dynamically" obtained
>>> string length info.  In cases when the info is not available
>>> the former calls the latter.
>>>
>>> The other new functions in tree-ssa-strlen.c are
>>> check_and_optimize_call and handle_integral_assign: I added
>>> them only to keep the check_and_optimize_stmt function from
>>> getting excessively long and hard to follow.  Otherwise,
>>> the code in the functions is unchanged.
>>>
>>> There are a number of further enhancements to consider as
>>> the next steps:
>>>    *  enhance the strlen module to determine string length range
>>>       information from integer variable to which it was previously
>>>       assigned (necessary to fully resolve pr83431 and pr90625),
>>>    *  make the sprintf/snprintf string length results directly
>>>       available to the strlen module,
>>>    *  enhance the sprintf module to optimize snprintf(0, 0, fmt)
>>>       calls with fmt strings consisting solely of plain characters
>>>       and %s directives to series of strlen calls on the arguments,
>>>    *  and more.
>>>
>>> Martin
>>
>>
>> gcc-83431.diff
>>
>> 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-ssa-strlen.c (strlen_optimize): New variable.
>> 	(get_string_length): Add comments.
>> 	(get_range_strlen_dynamic): New functions.
>> 	(check_and_optimize_call): New function.
>> 	(handle_integral_assign): New function.
>> 	(strlen_check_and_optimize_stmt): Rename...
>> 	(check_and_optimize_stmt): ...to this.  Factor code out into
>> 	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.
>> 	(pass_data_strlen): Remove property not satisfied during an early run.
>> 	(pass_strlen::do_optimize): New data member.
>> 	(pass_strlen::set_pass_param): New member function.
>> 	(pass_strlen::gate): Update to handle printf calls.
>> 	(pass_strlen::execute): Initialize loop and scev optimizers.
>> 	(dump_strlen_info): New function.
>> 	* tree-ssa-strlen.h (get_range_strlen_dynamic): Declare.
>> 	(handle_printf_call): Same.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR c++/83431
>> 	* gcc.dg/strlenopt-63.c: New test.
>> 	* 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-sprintf-warn-21.c: New test.
>> 	* gcc.dg/tree-ssa/dump-4.c: New test.
>> 	* gcc.dg/tree-ssa/pr83501.c: Adjust pass name.
> 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.

Thanks
Martin
Richard Biener July 11, 2019, 9:11 a.m. UTC | #7
On Thu, Jul 11, 2019 at 1:54 AM Martin Sebor <msebor@gmail.com> wrote:
>
> On 7/2/19 4:38 PM, Jeff Law wrote:
> > On 7/1/19 7:47 PM, Martin Sebor wrote:
> >> Attached is a more complete solution that fully resolves the bug
> >> report by avoiding a warning in cases like:
> >>
> >>    char a[32], b[8];
> >>
> >>    void f (void)
> >>    {
> >>      if (strlen (a) < sizeof b - 2)
> >>        snprintf (b, sizeof b, "b=%s", a); // no -Wformat-truncation
> >>    }
> >>
> >> It does that by having get_range_strlen_dynamic use the EVRP
> >> information for non-constant strlen calls: EVRP has recorded
> >> that the result is less sizeof b - 2 and so the function
> >> returns this limited range of lengths to snprintf which can
> >> then avoid warning.  It also improves the checking and can
> >> find latent bugs it missed before (like the off-by-one in
> >> print-rtl.c).
> >>
> >> Besides improving the accuracy of the -Wformat-overflow and
> >> truncation warnings this can also result in better code.
> >> So far this only benefits snprintf but there may be other
> >> opportunities to string functions as well (e.g., strcmp or
> >> memcmp).
> >>
> >> Jeff, I looked into your question/suggestion for me last week
> >> when we spoke, to introduce some sort of a recursion limit for
> >> get_range_strlen_dynamic.  It's easily doable but before we go
> >> down that path I did some testing to see how bad it can get and
> >> to compare it with get_range_strlen.  Here are the results for
> >> a few packages.  The dept is the maximum recursion depth, success
> >> and fail are the numbers of successful and failed calls to
> >> the function:
> >>
> >>    binutils-gdb:
> >>                                depth   success     fail
> >>      get_range_strlen:           319      8302    21621
> >>      get_range_strlen_dynamic:    41      1503      161
> >>    gcc:
> >>      get_range_strlen:            46      7211    11365
> >>      get_range_strlen_dynamic:    23     10272       12
> >>    glibc:
> >>      get_range_strlen:            76      2840    11422
> >>      get_range_strlen_dynamic:    51      1186       46
> >>    elfutils:
> >>      get_range_strlen:            33      1198     2516
> >>      get_range_strlen_dynamic:    31       685       36
> >>    kernel:
> >>      get_range_strlen:            25      5299    11698
> >>      get_range_strlen_dynamic:    31      9911      122
> >>
> >> Except the first case of get_range_strlen (I haven't looked into
> >> it yet), it doesn't seem too bad, and with just one exception it's
> >> better than get_range_strlen.  Let me know if you think it's worth
> >> adding a parameter (I assume we'd use it for both functions) and
> >> what to set it to.
> >>
> >> On 6/11/19 5:26 PM, Martin Sebor wrote:
> >>> The sprintf and strlen passes both work with strings but
> >>> run independently of one another and don't share state.  As
> >>> a result, lengths of strings dynamically created by functions
> >>> that are available to the strlen pass are not available to
> >>> sprintf.  Conversely, lengths of strings formatted by
> >>> the sprintf functions are not made available to the strlen
> >>> pass.  The result is less efficient code, poor diagnostics,
> >>> and ultimately less than optimal user experience.
> >>>
> >>> The attached patch is the first step toward rectifying this
> >>> design problem.  It integrates the two passes into one and
> >>> exposes the string length data managed by the strlen pass to
> >>> the sprintf "module."  (It does not expose any sprintf data
> >>> to the strlen pass yet.)
> >>>
> >>> The sprintf pass invocations in passes.def have been replaced
> >>> with those of strlen.  The first "early" invocation is only
> >>> effective for the sprintf module to enable warnings without
> >>> optimization.  The second invocation is "late" and enables
> >>> both warnings and the sprintf and strlen optimizations unless
> >>> explicitly disabled via -fno-optimize-strlen.
> >>>
> >>> Since the strlen optimization is the second invocation of
> >>> the pass tests that scan the strlen dump have been adjusted
> >>> to look for the "strlen2" dump file.
> >>>
> >>> The changes in the patch are mostly mechanical.  The one new
> >>> "feature" worth calling out is the get_range_strlen_dynamic
> >>> function.  It's analogous to get_range_strlen in gimple-fold.c
> >>> except that it makes use of the strlen "dynamically" obtained
> >>> string length info.  In cases when the info is not available
> >>> the former calls the latter.
> >>>
> >>> The other new functions in tree-ssa-strlen.c are
> >>> check_and_optimize_call and handle_integral_assign: I added
> >>> them only to keep the check_and_optimize_stmt function from
> >>> getting excessively long and hard to follow.  Otherwise,
> >>> the code in the functions is unchanged.
> >>>
> >>> There are a number of further enhancements to consider as
> >>> the next steps:
> >>>    *  enhance the strlen module to determine string length range
> >>>       information from integer variable to which it was previously
> >>>       assigned (necessary to fully resolve pr83431 and pr90625),
> >>>    *  make the sprintf/snprintf string length results directly
> >>>       available to the strlen module,
> >>>    *  enhance the sprintf module to optimize snprintf(0, 0, fmt)
> >>>       calls with fmt strings consisting solely of plain characters
> >>>       and %s directives to series of strlen calls on the arguments,
> >>>    *  and more.
> >>>
> >>> Martin
> >>
> >>
> >> gcc-83431.diff
> >>
> >> 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-ssa-strlen.c (strlen_optimize): New variable.
> >>      (get_string_length): Add comments.
> >>      (get_range_strlen_dynamic): New functions.
> >>      (check_and_optimize_call): New function.
> >>      (handle_integral_assign): New function.
> >>      (strlen_check_and_optimize_stmt): Rename...
> >>      (check_and_optimize_stmt): ...to this.  Factor code out into
> >>      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.
> >>      (pass_data_strlen): Remove property not satisfied during an early run.
> >>      (pass_strlen::do_optimize): New data member.
> >>      (pass_strlen::set_pass_param): New member function.
> >>      (pass_strlen::gate): Update to handle printf calls.
> >>      (pass_strlen::execute): Initialize loop and scev optimizers.
> >>      (dump_strlen_info): New function.
> >>      * tree-ssa-strlen.h (get_range_strlen_dynamic): Declare.
> >>      (handle_printf_call): Same.
> >>
> >> gcc/testsuite/ChangeLog:
> >>
> >>      PR c++/83431
> >>      * gcc.dg/strlenopt-63.c: New test.
> >>      * 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-sprintf-warn-21.c: New test.
> >>      * gcc.dg/tree-ssa/dump-4.c: New test.
> >>      * gcc.dg/tree-ssa/pr83501.c: Adjust pass name.
> > 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)

I don't see the recursion in maybe_fold_{and,or}_comparisons.

The others all smell like they might be yours ;)  (besides object-size
maybe but that one is limited by having a cache - hopefully also
used when not used in the compute-everything mode).

> 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.

It's never an excuse to add new cases though and adding a limit
is _really_ simple.

Richard.

> >   +  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.
>
> Thanks
> Martin
Jakub Jelinek July 11, 2019, 9:25 a.m. UTC | #8
On Thu, Jul 11, 2019 at 11:11:58AM +0200, Richard Biener wrote:
> The others all smell like they might be yours ;)  (besides object-size
> maybe but that one is limited by having a cache - hopefully also
> used when not used in the compute-everything mode).

objsz doesn't really have a compute everything mode, it has a mode where the
caches are available and a mode where they aren't.  If they are available,
it will compute whatever it needs to and anything needed to compute that and
cache.

If they aren't available, for optimize > 0 it will just punt on
SSA_NAMEs (defers until it is done with the caches), for -O0 it looks
through SSA_NAME_DEF_STMTs of POINTER_PLUS_EXPR with constant as second
argument, and that one indeed is inbounded, so if one has a million of
POINTER_PLUS_EXPRs stmts, each adding 1 to the previous SSA_NAME, yes, it
will be a pain if every one of those SSA_NAMEs is then passed to some
__builtin_object_size too.  Guess we could add a limit there too, no reason
to look through more than a dozen or two at most.

	Jakub
Martin Sebor July 12, 2019, 12:08 a.m. UTC | #9
...
>>> 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)
> 
> I don't see the recursion in maybe_fold_{and,or}_comparisons.

I didn't study any of these very carefully, I just looked for uses
of SSA_NAME_DEF_STMT around recursive calls.  same_bool_comparison_p
looks like it calls itself with that result.  I could be wrong.

> 
> The others all smell like they might be yours ;)  (besides object-size
> maybe but that one is limited by having a cache - hopefully also
> used when not used in the compute-everything mode).

It doesn't matter who introduced them.  What I'm saying is that
if the recursion is a problem it should be limited everywhere,
not just in one place.  A change with global scope like that would
best be made independently of other unrelated changes, to minimize
the risk of introducing bugs and then the difficulty of debugging
and fixing them.

>> 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.
> 
> It's never an excuse to add new cases though and adding a limit
> is _really_ simple.

An "excuse?"  I'm just explaining that I would prefer to introduce
the limit independently of the integration.

Sure, it is simple in that one place.  I said that above.  It's
also probably relatively simple in each of the other instances
(at least those I'm familiar with).  It's just cleaner and safer
to add it independently of other changes, that's all.

Anyway, I posted a simple patch to introduce the limit.  I'm also
changing the integrated pass to make use of it once it's committed.
If it's considered necessary I'm also willing to adjust the rest
of the algorithms I introduced to respect the limit.

Thanks
Martin
Martin Sebor July 12, 2019, 9:19 p.m. UTC | #10
...
>> 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
Jeff Law Aug. 9, 2019, 4:42 p.m. UTC | #11
On 7/1/19 7:47 PM, Martin Sebor wrote:
> 
> Jeff, I looked into your question/suggestion for me last week
> when we spoke, to introduce some sort of a recursion limit for
> get_range_strlen_dynamic.  It's easily doable but before we go
> down that path I did some testing to see how bad it can get and
> to compare it with get_range_strlen.  Here are the results for
> a few packages.  The dept is the maximum recursion depth, success
> and fail are the numbers of successful and failed calls to
> the function:
> 
>   binutils-gdb:
>                               depth   success     fail
>     get_range_strlen:           319      8302    21621
>     get_range_strlen_dynamic:    41      1503      161
>   gcc:
>     get_range_strlen:            46      7211    11365
>     get_range_strlen_dynamic:    23     10272       12
>   glibc:
>     get_range_strlen:            76      2840    11422
>     get_range_strlen_dynamic:    51      1186       46
>   elfutils:
>     get_range_strlen:            33      1198     2516
>     get_range_strlen_dynamic:    31       685       36
>   kernel:
>     get_range_strlen:            25      5299    11698
>     get_range_strlen_dynamic:    31      9911      122
> 
> Except the first case of get_range_strlen (I haven't looked into
> it yet), it doesn't seem too bad, and with just one exception it's
> better than get_range_strlen.  Let me know if you think it's worth
> adding a parameter (I assume we'd use it for both functions) and
> what to set it to.
So I know you've added a limiter, but just to close on this topic.  I
generally agree with Richi on this -- we've regularly seen instances
where these pathological cases occur in the wild.  For the most part I
wouldn't consider any of the codebases above to be good at finding those
pathological cases.  They are reasonably good for finding the inflection
point for diminishing gains though.

For codes which walk PHI nodes Bradly Lucier's testcases are often
useful (there's many in BZ in various states).  His testcases tend to
have both lots of PHI nodes and PHI nodes with many arguments.  I don't
recall if the depth of any given use-def chain in those tests are
terribly long though.

One could ponder going through the old compile-time hogs in the BZ
database and using that to build some kind of testsuite for these kinds
of issues.  I question if we could reliably run them from the dejagnu
suite without significant cost, but they might be useful when trying to
evaluate stuff like if we've got reasonable clamps on recursive walks,
absurd loop nests, etc.


jeff
Jeff Law Aug. 9, 2019, 5:07 p.m. UTC | #12
On 7/10/19 5:54 PM, Martin Sebor wrote:
>> 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.
OK.  Just wanted to make sure I understood it's structure at the highest
level.


> 
>>> 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.
I wasn't necessarily referring to any of the strlen/sprintf code when I
made that comment.  I was thinking more about DOM and jump threading
where I think I've got extraneous "class" all over the place.  And I
can't recall ever auditing for const-correctness.  Both are probably
worth fixing.


> 
>>> 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.
OK.  Thanks for clarifying.  That's probably why we have the someone
unusual gating tests.


> 
>>
>> 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=]
That does sound familiar.  But ISTM in a non-optimized case we could
still just run the late one and get the warning.  It would seem the
problem with that is the late pass is inside the pass_all_optimizations
in passes.def.

We'd probably have to close the pass_all_optimizations, do the sprintf
checking, then open a new pass_all_optimizations_something for the rest
of the pipeline currently under pass_all_optimizations.

Seems out of scope for now, but worth remembering.


> 
>> 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.
It's more about existing conventions and consistency in the codebase.
We've used NULL to refer to "null pointer" for decades.

> 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.
So I think we can come back to the passes above and add limiters to them
as a distinct patchkit.  And I think we will ultimately want those limiters.

>>> @@ -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.
I probably should have been clearer.  Let's go ahead and put in a prefix
for now, but the better long term plan is to classify the pass, address
const-correctness, initialization points, etc  Each of which should
probably happen independently.  I happen to have a patch which does the
classification, so that would fall to me naturally.


> 
>>> @@ -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.
So I'm not really comfortable dropping PROP_ssa.  Basically I can't
convince myself that doing so won't ultimately drop PROP_ssa from the
current function's properties.  Which in turn would turn off certain
sanity checkers in passes.def.

What I think this argues is that you want to register two distinct
passes.  They would differ in their "name" and "properties required"
fields.  You may also be able to clean up the gating with that change as
well.  They obviously share a ton of underlying code.






>>
>>
>>
>>> +
>>>   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.
Ah, it's just moved from the sprintf pass into strlen.  I see what
happened now.

Still have to review the updated patch, but wanted to close on some of
the issues we were already discussing.

jeff
Martin Sebor Aug. 12, 2019, 10:09 p.m. UTC | #13
On 8/9/19 11:07 AM, Jeff Law wrote:
> On 7/10/19 5:54 PM, Martin Sebor wrote:
>>> 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.
> OK.  Just wanted to make sure I understood it's structure at the highest
> level.
> 
> 
>>
>>>> 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.
> I wasn't necessarily referring to any of the strlen/sprintf code when I
> made that comment.  I was thinking more about DOM and jump threading
> where I think I've got extraneous "class" all over the place.  And I
> can't recall ever auditing for const-correctness.  Both are probably
> worth fixing.
> 
> 
>>
>>>> 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.
> OK.  Thanks for clarifying.  That's probably why we have the someone
> unusual gating tests.
> 
> 
>>
>>>
>>> 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=]
> That does sound familiar.  But ISTM in a non-optimized case we could
> still just run the late one and get the warning.  It would seem the
> problem with that is the late pass is inside the pass_all_optimizations
> in passes.def.
> 
> We'd probably have to close the pass_all_optimizations, do the sprintf
> checking, then open a new pass_all_optimizations_something for the rest
> of the pipeline currently under pass_all_optimizations.
> 
> Seems out of scope for now, but worth remembering.
> 
> 
>>
>>> 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.
> It's more about existing conventions and consistency in the codebase.
> We've used NULL to refer to "null pointer" for decades.
> 
>> 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.
> So I think we can come back to the passes above and add limiters to them
> as a distinct patchkit.  And I think we will ultimately want those limiters.
> 
>>>> @@ -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.
> I probably should have been clearer.  Let's go ahead and put in a prefix
> for now, but the better long term plan is to classify the pass, address
> const-correctness, initialization points, etc  Each of which should
> probably happen independently.  I happen to have a patch which does the
> classification, so that would fall to me naturally.

I assume you want me to add the strlen_ prefix back so I did that.

The function used to be called strlen_optimize_call.  You made me
change it to strlen_check_and_optimize_call when I added warnings
to the pass.  I dropped the "strlen_" part in this patch because
the function serves both "passes" (i.e., strlen and sprintf).

FWIW, this is a static function with a single caller just a little
bit later in the file so there's no risk that it will be called
from anywhere else with the wrong expectations.  There is no also
discernible convention for static function names (or any others)
in any pass that I've looked at, so from where I sit it doesn't
matter what this one is called.

>>>> @@ -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.
> So I'm not really comfortable dropping PROP_ssa.  Basically I can't
> convince myself that doing so won't ultimately drop PROP_ssa from the
> current function's properties.  Which in turn would turn off certain
> sanity checkers in passes.def.
> 
> What I think this argues is that you want to register two distinct
> passes.  They would differ in their "name" and "properties required"
> fields.  You may also be able to clean up the gating with that change as
> well.  They obviously share a ton of underlying code.

Okay, I did that.  AFAICS, PROP_ssa controls running verify_ssa()
in tree-ssa.c so it will do more checking than without.

Attached is the updated patch with the two changes.

Martin

>>>> +
>>>>    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.
> Ah, it's just moved from the sprintf pass into strlen.  I see what
> happened now.
> 
> Still have to review the updated patch, but wanted to close on some of
> the issues we were already discussing.
> 
> jeff
>
Jeff Law Aug. 23, 2019, 2:13 a.m. UTC | #14
On 8/12/19 4:09 PM, Martin Sebor wrote:

> 
> gcc-83431.diff
> 
> 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.
> 
> 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.
> 
>
OK for the trunk.  Sorry this took so long.

Jeff
Christophe Lyon Aug. 27, 2019, 9:09 a.m. UTC | #15
On Fri, 23 Aug 2019 at 04:14, Jeff Law <law@redhat.com> wrote:
>
> On 8/12/19 4:09 PM, Martin Sebor wrote:
>
> >
> > gcc-83431.diff
> >
> > 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.
> >
> > 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.
> >
> >
> OK for the trunk.  Sorry this took so long.
>

Hi!

It seems this patch is causing bootstrap failure on arm (and
apparently on i686 according to gcc-testresults).
I've noticed:
00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1743:24:
error: ‘(c)=c ## ’ directive output between 9 and 9 bytes may
cause result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
00:27:03  1743 |       sprintf (buf, "%s(c)=c ## %s", macro, suffix);
00:27:03       |                        ^~~~~~~~~
00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1738:24:
error: ‘(c)=c’ directive output between 5 and 5 bytes may cause
result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
00:27:03  1738 |       sprintf (buf, "%s(c)=c", macro);
00:27:03       |                        ^~~~~
00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c/gimple-parser.c:
In function ‘void c_parser_parse_gimple_body(c_parser*, char*,
c_declspec_il, profile_count)’:
00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c/gimple-parser.c:212:1:
note: parameter passing for argument of type ‘profile_count’
changed in GCC 9.1
00:27:03   212 | c_parser_parse_gimple_body (c_parser *cparser, char
*gimple_pass,
00:27:03       | ^~~~~~~~~~~~~~~~~~~~~~~~~~
00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:
In function ‘void builtin_define_type_minmax(const char*, const
char*, tree)’:
00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1823:21:
error: ‘%s’ directive output between 0 and 131 bytes may cause
result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
00:27:03  1823 |   sprintf (buf, "%s=%s%s", max_macro, value, suffix);
00:27:03       |                     ^~                ~~~~~
00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1838:21:
error: ‘=(-’ directive output between 3 and 3 bytes may cause
result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
00:27:03  1838 |    sprintf (buf, "%s=(-%s - 1)", min_macro, max_macro);
00:27:03       |                     ^~~
00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1832:21:
error: ‘=0’ directive output between 2 and 2 bytes may cause
result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
00:27:03  1832 |    sprintf (buf, "%s=0%s", min_macro, suffix);
00:27:03       |                     ^~

On arm still, I've noticed failures to build glibc-2.29:
res_query.c: In function '__res_context_querydomain':
res_query.c:591:18: error: '%s' directive writing up to 2147483646
bytes into a region of size 1025 [-Werror=format-overflow=]
  591 |   sprintf(nbuf, "%s.%s", name, domain);
      |                  ^~
res_query.c:591:3: note: 'sprintf' output between 2 and 4294967293
bytes into a destination of size 1025
  591 |   sprintf(nbuf, "%s.%s", name, domain);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Can you have a look?

Thanks
Christophe


> Jeff
Martin Sebor Aug. 27, 2019, 3:43 p.m. UTC | #16
On 8/27/19 3:09 AM, Christophe Lyon wrote:
> On Fri, 23 Aug 2019 at 04:14, Jeff Law <law@redhat.com> wrote:
>>
>> On 8/12/19 4:09 PM, Martin Sebor wrote:
>>
>>>
>>> gcc-83431.diff
>>>
>>> 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.
>>>
>>> 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.
>>>
>>>
>> OK for the trunk.  Sorry this took so long.
>>
> 
> Hi!
> 
> It seems this patch is causing bootstrap failure on arm (and
> apparently on i686 according to gcc-testresults).
> I've noticed:
> 00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1743:24:
> error: ‘(c)=c ## ’ directive output between 9 and 9 bytes may
> cause result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
> 00:27:03  1743 |       sprintf (buf, "%s(c)=c ## %s", macro, suffix);
> 00:27:03       |                        ^~~~~~~~~
> 00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1738:24:
> error: ‘(c)=c’ directive output between 5 and 5 bytes may cause
> result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
> 00:27:03  1738 |       sprintf (buf, "%s(c)=c", macro);
> 00:27:03       |                        ^~~~~
> 00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c/gimple-parser.c:
> In function ‘void c_parser_parse_gimple_body(c_parser*, char*,
> c_declspec_il, profile_count)’:
> 00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c/gimple-parser.c:212:1:
> note: parameter passing for argument of type ‘profile_count’
> changed in GCC 9.1
> 00:27:03   212 | c_parser_parse_gimple_body (c_parser *cparser, char
> *gimple_pass,
> 00:27:03       | ^~~~~~~~~~~~~~~~~~~~~~~~~~
> 00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:
> In function ‘void builtin_define_type_minmax(const char*, const
> char*, tree)’:
> 00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1823:21:
> error: ‘%s’ directive output between 0 and 131 bytes may cause
> result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
> 00:27:03  1823 |   sprintf (buf, "%s=%s%s", max_macro, value, suffix);
> 00:27:03       |                     ^~                ~~~~~
> 00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1838:21:
> error: ‘=(-’ directive output between 3 and 3 bytes may cause
> result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
> 00:27:03  1838 |    sprintf (buf, "%s=(-%s - 1)", min_macro, max_macro);
> 00:27:03       |                     ^~~
> 00:27:03 /home/tcwg-buildslave/workspace/tcwg_gnu_0/abe/snapshots/gcc.git~master/gcc/c-family/c-cppbuiltin.c:1832:21:
> error: ‘=0’ directive output between 2 and 2 bytes may cause
> result to exceed ‘INT_MAX’ [-Werror=format-overflow=]
> 00:27:03  1832 |    sprintf (buf, "%s=0%s", min_macro, suffix);
> 00:27:03       |                     ^~
> 
> On arm still, I've noticed failures to build glibc-2.29:
> res_query.c: In function '__res_context_querydomain':
> res_query.c:591:18: error: '%s' directive writing up to 2147483646
> bytes into a region of size 1025 [-Werror=format-overflow=]
>    591 |   sprintf(nbuf, "%s.%s", name, domain);
>        |                  ^~
> res_query.c:591:3: note: 'sprintf' output between 2 and 4294967293
> bytes into a destination of size 1025
>    591 |   sprintf(nbuf, "%s.%s", name, domain);
>        |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> Can you have a look?

I can reproduce the ILP32 warnings.  I suspect those in the ARM
Glibc build are caused by the same issue as in the i386 build.
On an LP64 host with an ILP32 target the range of lengths of
an unknown string is computed as [0, PTRDIFF_MAX - 2] when on
LP64 it's [0, SIZE_MAX].  The caller expects only the latter
to mean an unknown string with any length.

Martin
diff mbox series

Patch

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.
	* tree-ssa-strlen.c (strlen_optimize): New variable.
	(get_string_length): Add comments.
	(get_range_strlen_dynamic): New functions.
	(check_and_optimize_call): New function.
	(handle_integral_assign): New function.
	(strlen_check_and_optimize_stmt): Rename...
	(check_and_optimize_stmt): ...to this.  Factor code out into
	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.
	(pass_data_strlen): Remove property not satisfied during an early run.
	(pass_strlen::do_optimize): New data member.
	(pass_strlen::set_pass_param): New member function.
	(pass_strlen::gate): Update to handle printf calls.
	(pass_strlen::execute): Initialize loop and scev optimizers.
	* tree-ssa-strlen.h (get_range_strlen_dynamic): Declare.
	(handle_printf_call): Same.

gcc/testsuite/ChangeLog:

	PR c++/83431
	* gcc.dg/strlenopt-63.c: New test.
	* gcc.dg/strlenopt-1.c: Adjust pass name.
	* 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-sprintf-warn-21.c: New test.
	* gcc.dg/tree-ssa/dump-4.c: New test.

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 80b0bf825ef..27270c50536 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -86,6 +86,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "alloc-pool.h"
 #include "vr-values.h"
 #include "gimple-ssa-evrp-analyze.h"
+#include "tree-ssa-strlen.h"
 
 /* The likely worst case value of MB_LEN_MAX for the target, large enough
    for UTF-8.  Ideally, this would be obtained by a target hook if it were
@@ -100,80 +101,15 @@  along with GCC; see the file COPYING3.  If not see
 
 namespace {
 
-const pass_data pass_data_sprintf_length = {
-  GIMPLE_PASS,             // pass type
-  "printf-return-value",   // pass name
-  OPTGROUP_NONE,           // optinfo_flags
-  TV_NONE,                 // tv_id
-  PROP_cfg,                // properties_required
-  0,	                   // properties_provided
-  0,	                   // properties_destroyed
-  0,	                   // properties_start
-  0,	                   // properties_finish
-};
-
 /* Set to the warning level for the current function which is equal
    either to warn_format_trunc for bounded functions or to
    warn_format_overflow otherwise.  */
 
 static int warn_level;
 
+struct call_info;
 struct format_result;
 
-class sprintf_dom_walker : public dom_walker
-{
- public:
-  sprintf_dom_walker ()
-    : dom_walker (CDI_DOMINATORS),
-      evrp_range_analyzer (false) {}
-  ~sprintf_dom_walker () {}
-
-  edge before_dom_children (basic_block) FINAL OVERRIDE;
-  void after_dom_children (basic_block) FINAL OVERRIDE;
-  bool handle_gimple_call (gimple_stmt_iterator *);
-
-  struct call_info;
-  bool compute_format_length (call_info &, format_result *);
-  class evrp_range_analyzer evrp_range_analyzer;
-};
-
-class pass_sprintf_length : public gimple_opt_pass
-{
-  bool fold_return_value;
-
-public:
-  pass_sprintf_length (gcc::context *ctxt)
-    : gimple_opt_pass (pass_data_sprintf_length, ctxt),
-    fold_return_value (false)
-  { }
-
-  opt_pass * clone () { return new pass_sprintf_length (m_ctxt); }
-
-  virtual bool gate (function *);
-
-  virtual unsigned int execute (function *);
-
-  void set_pass_param (unsigned int n, bool param)
-    {
-      gcc_assert (n == 0);
-      fold_return_value = param;
-    }
-
-};
-
-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);
-}
-
 /* The minimum, maximum, likely, and unlikely maximum number of bytes
    of output either a formatting function or an individual directive
    can result in.  */
@@ -683,7 +619,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);
 
 /* Description of a format directive.  A directive is either a plain
    string or a conversion specification that starts with '%'.  */
@@ -718,7 +654,7 @@  struct directive
 
   /* Format conversion function that given a directive and an argument
      returns the formatting result.  */
-  fmtresult (*fmtfunc) (const directive &, tree, vr_values *);
+  fmtresult (*fmtfunc) (const directive &, tree, const vr_values *);
 
   /* Return True when a the format flag CHR has been used.  */
   bool get_flag (char chr) const
@@ -755,7 +691,7 @@  struct directive
      or 0, whichever is greater.  For a non-constant ARG in some range
      set width to its range adjusting each bound to -1 if it's less.
      For an indeterminate ARG set width to [0, INT_MAX].  */
-  void set_width (tree arg, vr_values *vr_values)
+  void set_width (tree arg, const vr_values *vr_values)
   {
     get_int_range (arg, width, width + 1, true, 0, vr_values);
   }
@@ -771,7 +707,7 @@  struct directive
      or -1 whichever is greater.  For a non-constant ARG in some range
      set precision to its range adjusting each bound to -1 if it's less.
      For an indeterminate ARG set precision to [-1, INT_MAX].  */
-  void set_precision (tree arg, vr_values *vr_values)
+  void set_precision (tree arg, const vr_values *vr_values)
   {
     get_int_range (arg, prec, prec + 1, false, -1, vr_values);
   }
@@ -903,7 +839,7 @@  bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
 
 /* Description of a call to a formatted function.  */
 
-struct sprintf_dom_walker::call_info
+struct call_info
 {
   /* Function call statement.  */
   gimple *callstmt;
@@ -977,7 +913,7 @@  struct sprintf_dom_walker::call_info
 /* Return the result of formatting a no-op directive (such as '%n').  */
 
 static fmtresult
-format_none (const directive &, tree, vr_values *)
+format_none (const directive &, tree, const vr_values *)
 {
   fmtresult res (0);
   return res;
@@ -986,7 +922,7 @@  format_none (const directive &, tree, vr_values *)
 /* Return the result of formatting the '%%' directive.  */
 
 static fmtresult
-format_percent (const directive &, tree, vr_values *)
+format_percent (const directive &, tree, const vr_values *)
 {
   fmtresult res (1);
   return res;
@@ -1044,7 +980,7 @@  build_intmax_type_nodes (tree *pintmax, tree *puintmax)
 static bool
 get_int_range (tree arg, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
 	       bool absolute, HOST_WIDE_INT negbound,
-	       class vr_values *vr_values)
+	       const class vr_values *vr_values)
 {
   /* The type of the result.  */
   const_tree type = integer_type_node;
@@ -1083,7 +1019,8 @@  get_int_range (tree arg, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
 	  && TYPE_PRECISION (argtype) <= TYPE_PRECISION (type))
 	{
 	  /* Try to determine the range of values of the integer argument.  */
-	  value_range *vr = vr_values->get_value_range (arg);
+	  const value_range *vr
+	    = CONST_CAST (class vr_values *, vr_values)->get_value_range (arg);
 	  if (range_int_cst_p (vr))
 	    {
 	      HOST_WIDE_INT type_min
@@ -1200,7 +1137,7 @@  adjust_range_for_overflow (tree dirtype, tree *argmin, tree *argmax)
    used when the directive argument or its value isn't known.  */
 
 static fmtresult
-format_integer (const directive &dir, tree arg, vr_values *vr_values)
+format_integer (const directive &dir, tree arg, const vr_values *vr_values)
 {
   tree intmax_type_node;
   tree uintmax_type_node;
@@ -1383,7 +1320,8 @@  format_integer (const directive &dir, tree arg, vr_values *vr_values)
     {
       /* Try to determine the range of values of the integer argument
 	 (range information is not available for pointers).  */
-      value_range *vr = vr_values->get_value_range (arg);
+      const value_range *vr
+	= CONST_CAST (class vr_values *, vr_values)->get_value_range (arg);
       if (range_int_cst_p (vr))
 	{
 	  argmin = vr->min ();
@@ -1833,7 +1771,7 @@  format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
    ARG.  */
 
 static fmtresult
-format_floating (const directive &dir, tree arg, vr_values *)
+format_floating (const directive &dir, tree arg, const vr_values *)
 {
   HOST_WIDE_INT prec[] = { dir.prec[0], dir.prec[1] };
   tree type = (dir.modifier == FMT_LEN_L || dir.modifier == FMT_LEN_ll
@@ -2032,16 +1970,28 @@  get_string_length (tree str, unsigned eltsize)
   if (!str)
     return fmtresult ();
 
-  /* Determine the length of the shortest and longest string referenced
-     by STR.  Strings of unknown lengths are bounded by the sizes of
-     arrays that subexpressions of STR may refer to.  Pointers that
-     aren't known to point any such arrays result in LENDATA.MAXLEN
-     set to SIZE_MAX.  */
+  /* Try to determine the dynamic string length first.  */
   c_strlen_data lendata = { };
-  get_range_strlen (str, &lendata, eltsize);
+  if (eltsize == 1)
+    get_range_strlen_dynamic (str, &lendata);
+  else
+    {
+      /* Determine the length of the shortest and longest string referenced
+	 by STR.  Strings of unknown lengths are bounded by the sizes of
+	 arrays that subexpressions of STR may refer to.  Pointers that
+	 aren't known to point any such arrays result in LENDATA.MAXLEN
+	 set to SIZE_MAX.  */
+      get_range_strlen (str, &lendata, eltsize);
+    }
+
+  /* LENDATA.MAXBOUND is null when LENDATA.MIN corresponds to the shortest
+     string referenced by STR.  Otherwise, if it's not equal to .MINLEN it
+     corresponds to the bound of the largest array STR refers to, if known,
+     or it's SIZE_MAX otherwise. */
 
-  /* Return the default result when nothing is known about the string. */
-  if (integer_all_onesp (lendata.maxbound)
+  /* Return the default result when nothing is known about the string.  */
+  if (lendata.maxbound
+      && integer_all_onesp (lendata.maxbound)
       && integer_all_onesp (lendata.maxlen))
     return fmtresult ();
 
@@ -2051,7 +2001,7 @@  get_string_length (tree str, unsigned eltsize)
        : 0);
 
   HOST_WIDE_INT max
-    = (tree_fits_uhwi_p (lendata.maxbound)
+    = (lendata.maxbound && tree_fits_uhwi_p (lendata.maxbound)
        ? tree_to_uhwi (lendata.maxbound)
        : HOST_WIDE_INT_M1U);
 
@@ -2090,10 +2040,11 @@  get_string_length (tree str, unsigned eltsize)
   else
     {
       /* When the upper bound is unknown (it can be zero or excessive)
-	 set the likely length to the greater of 1 and the length of
-	 the shortest string and reset the lower bound to zero.  */
+	 set the likely length to the greater of 1.  If MAXBOUND is
+	 set, also reset the length of the lower bound to zero.  */
       res.range.likely = res.range.min ? res.range.min : warn_level > 1;
-      res.range.min = 0;
+      if (lendata.maxbound)
+	res.range.min = 0;
     }
 
   res.range.unlikely = unbounded ? HOST_WIDE_INT_MAX : res.range.max;
@@ -2107,7 +2058,7 @@  get_string_length (tree str, unsigned eltsize)
    vsprinf).  */
 
 static fmtresult
-format_character (const directive &dir, tree arg, vr_values *vr_values)
+format_character (const directive &dir, tree arg, const vr_values *vr_values)
 {
   fmtresult res;
 
@@ -2183,7 +2134,7 @@  format_character (const directive &dir, tree arg, vr_values *vr_values)
    vsprinf).  */
 
 static fmtresult
-format_string (const directive &dir, tree arg, vr_values *)
+format_string (const directive &dir, tree arg, const vr_values *)
 {
   fmtresult res;
 
@@ -2373,7 +2324,7 @@  format_string (const directive &dir, tree arg, vr_values *)
 /* Format plain string (part of the format string itself).  */
 
 static fmtresult
-format_plain (const directive &dir, tree, vr_values *)
+format_plain (const directive &dir, tree, const vr_values *)
 {
   fmtresult res (dir.len);
   return res;
@@ -2383,7 +2334,7 @@  format_plain (const directive &dir, tree, vr_values *)
    should be diagnosed given the AVAILable space in the destination.  */
 
 static bool
-should_warn_p (const sprintf_dom_walker::call_info &info,
+should_warn_p (const call_info &info,
 	       const result_range &avail, const result_range &result)
 {
   if (result.max <= avail.min)
@@ -2454,7 +2405,7 @@  should_warn_p (const sprintf_dom_walker::call_info &info,
 
 static bool
 maybe_warn (substring_loc &dirloc, location_t argloc,
-	    const sprintf_dom_walker::call_info &info,
+	    const call_info &info,
 	    const result_range &avail_range, const result_range &res,
 	    const directive &dir)
 {
@@ -2734,9 +2685,9 @@  maybe_warn (substring_loc &dirloc, location_t argloc,
    in *RES.  Return true if the directive has been handled.  */
 
 static bool
-format_directive (const sprintf_dom_walker::call_info &info,
+format_directive (const call_info &info,
 		  format_result *res, const directive &dir,
-		  class vr_values *vr_values)
+		  const class vr_values *vr_values)
 {
   /* Offset of the beginning of the directive from the beginning
      of the format string.  */
@@ -3083,10 +3034,10 @@  format_directive (const sprintf_dom_walker::call_info &info,
    the directive.  */
 
 static size_t
-parse_directive (sprintf_dom_walker::call_info &info,
+parse_directive (call_info &info,
 		 directive &dir, format_result *res,
 		 const char *str, unsigned *argno,
-		 vr_values *vr_values)
+		 const vr_values *vr_values)
 {
   const char *pcnt = strchr (str, target_percent);
   dir.beg = str;
@@ -3523,9 +3474,8 @@  parse_directive (sprintf_dom_walker::call_info &info,
    on, false otherwise (e.g., when a unknown or unhandled directive was seen
    that caused the processing to be terminated early).  */
 
-bool
-sprintf_dom_walker::compute_format_length (call_info &info,
-					   format_result *res)
+static bool
+compute_format_length (call_info &info, format_result *res, const vr_values *vr)
 {
   if (dump_file)
     {
@@ -3561,12 +3511,10 @@  sprintf_dom_walker::compute_format_length (call_info &info,
       directive dir = directive ();
       dir.dirno = dirno;
 
-      size_t n = parse_directive (info, dir, res, pf, &argno,
-				  evrp_range_analyzer.get_vr_values ());
+      size_t n = parse_directive (info, dir, res, pf, &argno, vr);
 
       /* Return failure if the format function fails.  */
-      if (!format_directive (info, res, dir,
-			     evrp_range_analyzer.get_vr_values ()))
+      if (!format_directive (info, res, dir, vr))
 	return false;
 
       /* Return success the directive is zero bytes long and it's
@@ -3614,7 +3562,7 @@  get_destination_size (tree dest)
    of its return values.  */
 
 static bool
-is_call_safe (const sprintf_dom_walker::call_info &info,
+is_call_safe (const call_info &info,
 	      const format_result &res, bool under4k,
 	      unsigned HOST_WIDE_INT retval[2])
 {
@@ -3673,7 +3621,7 @@  is_call_safe (const sprintf_dom_walker::call_info &info,
 
 static bool
 try_substitute_return_value (gimple_stmt_iterator *gsi,
-			     const sprintf_dom_walker::call_info &info,
+			     const call_info &info,
 			     const format_result &res)
 {
   tree lhs = gimple_get_lhs (info.callstmt);
@@ -3791,7 +3739,7 @@  try_substitute_return_value (gimple_stmt_iterator *gsi,
 
 static bool
 try_simplify_call (gimple_stmt_iterator *gsi,
-		   const sprintf_dom_walker::call_info &info,
+		   const call_info &info,
 		   const format_result &res)
 {
   unsigned HOST_WIDE_INT dummy[2];
@@ -3844,13 +3792,17 @@  get_user_idx_format (tree fndecl, unsigned *idx_args)
   return tree_to_uhwi (fmtarg) - 1;
 }
 
-/* Determine if a GIMPLE CALL is to one of the sprintf-like built-in
-   functions and if so, handle it.  Return true if the call is removed
-   and gsi_next should not be performed in the caller.  */
+}   /* Unnamed namespace.  */
+
+/* Determine if a GIMPLE call at *GSI is to one of the sprintf-like built-in
+   functions and if so, handle it.  Return true if the call is removed and
+   gsi_next should not be performed in the caller.  */
 
 bool
-sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
+handle_printf_call (gimple_stmt_iterator *gsi, evrp_range_analyzer &evrp)
 {
+  init_target_to_host_charmap ();
+
   call_info info = call_info ();
 
   info.callstmt = gsi_stmt (*gsi);
@@ -4116,7 +4068,7 @@  sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
 	  /* Try to determine the range of values of the argument
 	     and use the greater of the two at level 1 and the smaller
 	     of them at level 2.  */
-	  value_range *vr = evrp_range_analyzer.get_value_range (size);
+	  const value_range *vr = evrp.get_value_range (size);
 	  if (range_int_cst_p (vr))
 	    {
 	      unsigned HOST_WIDE_INT minsize = TREE_INT_CST_LOW (vr->min ());
@@ -4227,7 +4179,7 @@  sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
      never set to true again).  */
   res.posunder4k = posunder4k && dstptr;
 
-  bool success = compute_format_length (info, &res);
+  bool success = compute_format_length (info, &res, evrp.get_vr_values ());
   if (res.warned)
     gimple_set_no_warning (info.callstmt, true);
 
@@ -4253,71 +4205,3 @@  sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
 
   return call_removed;
 }
-
-edge
-sprintf_dom_walker::before_dom_children (basic_block bb)
-{
-  evrp_range_analyzer.enter (bb);
-  for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si); )
-    {
-      /* Iterate over statements, looking for function calls.  */
-      gimple *stmt = gsi_stmt (si);
-
-      /* First record ranges generated by this statement.  */
-      evrp_range_analyzer.record_ranges_from_stmt (stmt, false);
-
-      if (is_gimple_call (stmt) && handle_gimple_call (&si))
-	/* If handle_gimple_call returns true, the iterator is
-	   already pointing to the next statement.  */
-	continue;
-
-      gsi_next (&si);
-    }
-  return NULL;
-}
-
-void
-sprintf_dom_walker::after_dom_children (basic_block bb)
-{
-  evrp_range_analyzer.leave (bb);
-}
-
-/* Execute the pass for function FUN.  */
-
-unsigned int
-pass_sprintf_length::execute (function *fun)
-{
-  init_target_to_host_charmap ();
-
-  calculate_dominance_info (CDI_DOMINATORS);
-  bool use_scev = optimize > 0 && flag_printf_return_value;
-  if (use_scev)
-    {
-      loop_optimizer_init (LOOPS_NORMAL);
-      scev_initialize ();
-    }
-
-  sprintf_dom_walker sprintf_dom_walker;
-  sprintf_dom_walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun));
-
-  if (use_scev)
-    {
-      scev_finalize ();
-      loop_optimizer_finalize ();
-    }
-
-  /* Clean up object size info.  */
-  fini_object_sizes ();
-  return 0;
-}
-
-}   /* Unnamed namespace.  */
-
-/* Return a pointer to a pass object newly constructed from the context
-   CTXT.  */
-
-gimple_opt_pass *
-make_pass_sprintf_length (gcc::context *ctxt)
-{
-  return new pass_sprintf_length (ctxt);
-}
diff --git a/gcc/passes.def b/gcc/passes.def
index ad2efabd385..2c5305d8311 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);
   NEXT_PASS (pass_walloca, /*strict_mode_p=*/true);
   NEXT_PASS (pass_build_cgraph_edges);
   TERMINATE_PASS_LIST (all_lowering_passes)
@@ -307,14 +307,13 @@  along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_lower_vector_ssa);
       NEXT_PASS (pass_lower_switch);
       NEXT_PASS (pass_cse_reciprocals);
-      NEXT_PASS (pass_sprintf_length, true);
       NEXT_PASS (pass_reassoc, false /* insert_powi_p */);
       NEXT_PASS (pass_strength_reduction);
       NEXT_PASS (pass_split_paths);
       NEXT_PASS (pass_tracer);
       NEXT_PASS (pass_thread_jumps);
       NEXT_PASS (pass_dominator, false /* may_peel_loop_headers_p */);
-      NEXT_PASS (pass_strlen);
+      NEXT_PASS (pass_strlen, true);
       NEXT_PASS (pass_thread_jumps);
       NEXT_PASS (pass_vrp, false /* warn_array_bounds_p */);
       /* Threading can leave many const/copy propagations in the IL.
@@ -355,7 +354,7 @@  along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_object_sizes);
       /* Fold remaining builtins.  */
       NEXT_PASS (pass_fold_builtins);
-      NEXT_PASS (pass_sprintf_length, true);
+      NEXT_PASS (pass_strlen, true);
       /* Copy propagation also copy-propagates constants, this is necessary
          to forward object-size and builtin folding results properly.  */
       NEXT_PASS (pass_copy_prop);
diff --git a/gcc/testsuite/gcc.dg/strlenopt-1.c b/gcc/testsuite/gcc.dg/strlenopt-1.c
index 910ec672e96..feec97c8821 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-1.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-1.c
@@ -36,9 +36,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-10.c b/gcc/testsuite/gcc.dg/strlenopt-10.c
index 97167df959f..5956f07274f 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-10.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-10.c
@@ -69,14 +69,14 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
 /* avr has BIGGEST_ALIGNMENT 8, allowing fold_builtin_memory_op
    to expand the memcpy call at the end of fn2.  */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 8 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 7 "strlen" { target { avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "\\*q_\[0-9\]* = 32;" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(\[^\n\r\]*, 1\\)" 1 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 8 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 7 "strlen2" { target { avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "\\*q_\[0-9\]* = 32;" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(\[^\n\r\]*, 1\\)" 1 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-11.c b/gcc/testsuite/gcc.dg/strlenopt-11.c
index f7fa44bde35..c21241cf3c4 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-11.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-11.c
@@ -58,18 +58,18 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 3 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 3 "strlen2" } } */
 /* avr has BIGGEST_ALIGNMENT 8, allowing fold_builtin_memory_op
    to expand the memcpy call at the end of fn1.  */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 7 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 6 "strlen" { target { avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 7 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 6 "strlen2" { target { avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
 /* Where the memcpy is expanded, the assignemts to elements of l are
    propagated.  */
-/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.0. = " 1 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.6. = " 1 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.9. = " 1 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;" 3 "strlen" { target { avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.0. = " 1 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.6. = " 1 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.9. = " 1 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;" 3 "strlen2" { target { avr-*-* } } } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-13.c b/gcc/testsuite/gcc.dg/strlenopt-13.c
index 3502599b28c..1930fa8f403 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-13.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-13.c
@@ -55,19 +55,19 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 4 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 4 "strlen2" } } */
 /* avr has BIGGEST_ALIGNMENT 8, allowing fold_builtin_memory_op
    to expand the memcpy call at the end of fn1.  */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 7 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 6 "strlen" { target { avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 7 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 6 "strlen2" { target { avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
 /* Where the memcpy is expanded, the assignemts to elements of l are
    propagated.  */
-/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.0. = " 1 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.1. = " 1 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.5. = " 1 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.6. = " 1 "strlen" { target { ! avr-*-* } } } } */
-/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;" 4 "strlen" { target { avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.0. = " 1 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.1. = " 1 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.5. = " 1 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;\[\n\r\]*  l.6. = " 1 "strlen2" { target { ! avr-*-* } } } } */
+/* { dg-final { scan-tree-dump-times "  _\[0-9\]* = strlen \\(\[^\n\r\]*;" 4 "strlen2" { target { avr-*-* } } } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-14g.c b/gcc/testsuite/gcc.dg/strlenopt-14g.c
index 62a83bf8fd7..40b24a7f632 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-14g.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-14g.c
@@ -107,10 +107,10 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "mempcpy \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 2 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "mempcpy \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 2 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-14gf.c b/gcc/testsuite/gcc.dg/strlenopt-14gf.c
index 8b126fcb7ea..2d2ea045c0a 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-14gf.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-14gf.c
@@ -11,15 +11,15 @@ 
 /* Compared to strlenopt-14gf.c, strcpy_chk with string literal as
    second argument isn't being optimized by builtins.c into
    memcpy.  */
-/* { dg-final { scan-tree-dump-times "strlen \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__memcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__mempcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__strcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__strcat_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__stpcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "mempcpy \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 2 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__memcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__mempcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__strcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__strcat_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__stpcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "mempcpy \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 2 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-15.c b/gcc/testsuite/gcc.dg/strlenopt-15.c
index 827ea07b6ea..b712bdfad4c 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-15.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-15.c
@@ -51,9 +51,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 3 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 3 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-16g.c b/gcc/testsuite/gcc.dg/strlenopt-16g.c
index 0cf8410735a..3a7fd4be391 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-16g.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-16g.c
@@ -24,10 +24,10 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "mempcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 1 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "mempcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 1 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-17g.c b/gcc/testsuite/gcc.dg/strlenopt-17g.c
index 184e530788e..c7efd9052f5 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-17g.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-17g.c
@@ -47,10 +47,10 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 3 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "mempcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 1 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 3 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "mempcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 1 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-18g.c b/gcc/testsuite/gcc.dg/strlenopt-18g.c
index f734675ec40..578a1625caf 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-18g.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-18g.c
@@ -73,9 +73,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 2 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 2 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-19.c b/gcc/testsuite/gcc.dg/strlenopt-19.c
index 022ba8b4787..cdeaf7eed24 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-19.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-19.c
@@ -72,9 +72,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 6 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 6 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-1f.c b/gcc/testsuite/gcc.dg/strlenopt-1f.c
index 856774de7ad..02e69c24e5b 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-1f.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-1f.c
@@ -5,13 +5,13 @@ 
 #define FORTIFY_SOURCE 2
 #include "strlenopt-1.c"
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__memcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__strcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__strcat_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__stpcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__memcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__strcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__strcat_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__stpcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-2.c b/gcc/testsuite/gcc.dg/strlenopt-2.c
index fd59a3cd513..d42de623da1 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-2.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-2.c
@@ -40,9 +40,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 5 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 5 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-20.c b/gcc/testsuite/gcc.dg/strlenopt-20.c
index 7b483eaeac1..c0ecbbccf4a 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-20.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-20.c
@@ -86,9 +86,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-21.c b/gcc/testsuite/gcc.dg/strlenopt-21.c
index 05b85a49dde..3a57403e7ab 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-21.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-21.c
@@ -57,9 +57,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 3 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 3 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-22.c b/gcc/testsuite/gcc.dg/strlenopt-22.c
index b4ef772f0e5..e2933e158e7 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-22.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-22.c
@@ -31,9 +31,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-22g.c b/gcc/testsuite/gcc.dg/strlenopt-22g.c
index 9c5d020588f..67850fbe6c9 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-22g.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-22g.c
@@ -5,9 +5,9 @@ 
 #define USE_GNU
 #include "strlenopt-22.c"
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 1 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 1 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-24.c b/gcc/testsuite/gcc.dg/strlenopt-24.c
index 639501a53e0..d4a3b5a818d 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-24.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-24.c
@@ -13,4 +13,4 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-25.c b/gcc/testsuite/gcc.dg/strlenopt-25.c
index 89b60e3ebed..aab0026be05 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-25.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-25.c
@@ -14,4 +14,4 @@  main ()
   return len - len2;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-26.c b/gcc/testsuite/gcc.dg/strlenopt-26.c
index da2f465a5b5..b9cdb56141d 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-26.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-26.c
@@ -21,5 +21,5 @@  main (void)
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-27.c b/gcc/testsuite/gcc.dg/strlenopt-27.c
index c539edb821c..8c1762e3e6b 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-27.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-27.c
@@ -19,4 +19,4 @@  main (void)
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-28.c b/gcc/testsuite/gcc.dg/strlenopt-28.c
index 03fb01781bd..4cddc906a57 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-28.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-28.c
@@ -56,4 +56,4 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-29.c b/gcc/testsuite/gcc.dg/strlenopt-29.c
index fb4b4c9cc71..d7406e8e2f4 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-29.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-29.c
@@ -24,4 +24,4 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-2f.c b/gcc/testsuite/gcc.dg/strlenopt-2f.c
index 1e915dac928..addab117b4b 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-2f.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-2f.c
@@ -5,13 +5,13 @@ 
 #define FORTIFY_SOURCE 2
 #include "strlenopt-2.c"
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__memcpy_chk \\(" 5 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__strcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__strcat_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__stpcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__memcpy_chk \\(" 5 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__strcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__strcat_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__stpcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-3.c b/gcc/testsuite/gcc.dg/strlenopt-3.c
index f17779c0a66..9934df3cde1 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-3.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-3.c
@@ -53,12 +53,12 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
 /* { dg-final { scan-tree-dump-times "return 0" 3 "optimized" } } */
 /* { dg-final { scan-tree-dump-times "return 4" 1 "optimized" } } */
 /* { dg-final { scan-tree-dump-times "return 3" 1 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-30.c b/gcc/testsuite/gcc.dg/strlenopt-30.c
index a85df686ce2..75f28439f8a 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-30.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-30.c
@@ -60,4 +60,4 @@  _Bool f7(char *s)
   return (t1 == s);
 }
 
-/* { dg-final { scan-tree-dump-times "__builtin_strncmp" 5 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "__builtin_strncmp" 5 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-31g.c b/gcc/testsuite/gcc.dg/strlenopt-31g.c
index 45cc29c1024..957f028d088 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-31g.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-31g.c
@@ -4,6 +4,6 @@ 
 #define USE_GNU
 #include "strlenopt-31.c"
 
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-not "strlen \\(" "strlen" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-not "strlen \\(" "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-32.c b/gcc/testsuite/gcc.dg/strlenopt-32.c
index 08eb6bc2b08..c021aecd0a6 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-32.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-32.c
@@ -190,4 +190,4 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-33.c b/gcc/testsuite/gcc.dg/strlenopt-33.c
index 1e1c4dee1f8..1d75d30c36d 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-33.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-33.c
@@ -39,4 +39,4 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-33g.c b/gcc/testsuite/gcc.dg/strlenopt-33g.c
index 7d24d2bfc32..8bc027aefb6 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-33g.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-33g.c
@@ -40,5 +40,5 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 2 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 2 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-34.c b/gcc/testsuite/gcc.dg/strlenopt-34.c
index c9433c0399e..84116293382 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-34.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-34.c
@@ -35,4 +35,4 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-35.c b/gcc/testsuite/gcc.dg/strlenopt-35.c
index 03b3e13a8e2..6c17f9c1175 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-35.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-35.c
@@ -28,4 +28,4 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-4.c b/gcc/testsuite/gcc.dg/strlenopt-4.c
index 802e4ca7ce4..9f48646bef4 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-4.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-4.c
@@ -66,9 +66,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 3 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 3 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 3 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 3 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 3 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 3 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-48.c b/gcc/testsuite/gcc.dg/strlenopt-48.c
index 179edd82a43..dd556c817ac 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-48.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-48.c
@@ -31,5 +31,5 @@  void h (void)
     abort();
 }
 
-/* { dg-final { scan-tree-dump-times "strlen" 0 "optimized" } }
+/* { dg-final { scan-tree-dump-times "strlen2" 0 "optimized" } }
    { dg-final { scan-tree-dump-times "abort" 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-49.c b/gcc/testsuite/gcc.dg/strlenopt-49.c
index f901fd14b54..7da553159c5 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-49.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-49.c
@@ -45,7 +45,7 @@  int cmp88 (void)
   return cmp88;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen" 0 "gimple" } }
+/* { dg-final { scan-tree-dump-times "strlen2" 0 "gimple" } }
    { dg-final { scan-tree-dump-times "len0 = 0;" 1 "gimple" } }
    { dg-final { scan-tree-dump-times "len = 18;" 1 "gimple" } }
    { dg-final { scan-tree-dump-times "lenx = 8;" 1 "gimple" } }
diff --git a/gcc/testsuite/gcc.dg/strlenopt-4g.c b/gcc/testsuite/gcc.dg/strlenopt-4g.c
index 879d5666c90..f53964677e0 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-4g.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-4g.c
@@ -5,9 +5,9 @@ 
 #define USE_GNU
 #include "strlenopt-4.c"
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 5 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 5 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-4gf.c b/gcc/testsuite/gcc.dg/strlenopt-4gf.c
index 7f261b7d34d..66afc11bf5a 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-4gf.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-4gf.c
@@ -6,13 +6,13 @@ 
 #define FORTIFY_SOURCE 2
 #include "strlenopt-4.c"
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__memcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__strcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__strcat_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "__stpcpy_chk \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 5 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__memcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__strcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__strcat_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "__stpcpy_chk \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 5 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-5.c b/gcc/testsuite/gcc.dg/strlenopt-5.c
index a24aea44e8b..37fe4e221da 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-5.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-5.c
@@ -48,9 +48,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-50.c b/gcc/testsuite/gcc.dg/strlenopt-50.c
index 1d1d36808a9..59b990c9755 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-50.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-50.c
@@ -112,5 +112,5 @@  void test_array_ref (void)
   T (&b[16], 0);  T (&b[17], 0);  T (&b[18], 0);  T (&b[19], 0);
 }
 
-/* { dg-final { scan-tree-dump-times "strlen" 0 "gimple" } }
+/* { dg-final { scan-tree-dump-times "strlen2" 0 "gimple" } }
    { dg-final { scan-tree-dump-times "call_in_true_branch_not_eliminated" 0 "ccp1" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-51.c b/gcc/testsuite/gcc.dg/strlenopt-51.c
index 3d879f1329a..9c0b15801b0 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-51.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-51.c
@@ -84,4 +84,4 @@  void test_elim_a9_9 (unsigned i)
   T (0); T (1); T (2); T (3); T (4); T (5); T (6); T (7); T (8);
 }
 
-/* { dg-final { scan-tree-dump-times "strlen" 0 "gimple" } } */
+/* { dg-final { scan-tree-dump-times "strlen2" 0 "gimple" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-52.c b/gcc/testsuite/gcc.dg/strlenopt-52.c
index 03e063b435e..775f33dddee 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-52.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-52.c
@@ -284,5 +284,5 @@  void test_global_struct_struct_array (void)
   T (ssa[5].sa9[3].a6, 3);
 }
 
-/* { dg-final { scan-tree-dump-times "strlen" 0 "gimple" } }
+/* { dg-final { scan-tree-dump-times "strlen2" 0 "gimple" } }
    { dg-final { scan-tree-dump-times "call_in_true_branch_not_eliminated" 0 "ccp1" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-53.c b/gcc/testsuite/gcc.dg/strlenopt-53.c
index baa680d125b..8f514cadfac 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-53.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-53.c
@@ -112,5 +112,5 @@  void test_array_ref (void)
   T (&b[16], 0);  T (&b[17], 0);  T (&b[18], 0);  T (&b[19], 0);
 }
 
-/* { dg-final { scan-tree-dump-times "strlen" 0 "gimple" } }
+/* { dg-final { scan-tree-dump-times "strlen2" 0 "gimple" } }
    { dg-final { scan-tree-dump-times "call_in_true_branch_not_eliminated" 0 "ccp1" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-54.c b/gcc/testsuite/gcc.dg/strlenopt-54.c
index c38e7918f8b..ccf6e349046 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-54.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-54.c
@@ -105,5 +105,5 @@  void elim_after_init_memcpy (void)
   T ("AB\000CD", 0, "ab\000c", 4, 2);
 }
 
-/* { dg-final { scan-tree-dump-times "strlen" 0 "optimized" } }
+/* { dg-final { scan-tree-dump-times "strlen2" 0 "optimized" } }
    { dg-final { scan-tree-dump-times "call_in_true_branch_not_eliminated" 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-55.c b/gcc/testsuite/gcc.dg/strlenopt-55.c
index d5a02953d36..9696a4b87a2 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-55.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-55.c
@@ -224,7 +224,7 @@  const void test_large_string_size (void)
 }
 
 
-/* { dg-final { scan-tree-dump-times "strlen" 0 "gimple" } }
+/* { dg-final { scan-tree-dump-times "strlen2" 0 "gimple" } }
    { dg-final { scan-tree-dump-times "memcmp" 0 "gimple" } }
    { dg-final { scan-tree-dump-times "strcmp" 0 "gimple" } }
    { dg-final { scan-tree-dump-times "call_in_true_branch_not_eliminated" 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-56.c b/gcc/testsuite/gcc.dg/strlenopt-56.c
index 39a532bf8d4..2bf99bde543 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-56.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-56.c
@@ -45,6 +45,6 @@  void test_contents (void)
 }
 
 
-/* { dg-final { scan-tree-dump-times "strlen" 0 "gimple" } }
+/* { dg-final { scan-tree-dump-times "strlen2" 0 "gimple" } }
    { dg-final { scan-tree-dump-times "strcmp" 0 "gimple" } }
    { dg-final { scan-tree-dump-times "abort" 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-6.c b/gcc/testsuite/gcc.dg/strlenopt-6.c
index fbff14c4fdb..a6eab9c08dc 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-6.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-6.c
@@ -77,9 +77,9 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 7 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 7 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-61.c b/gcc/testsuite/gcc.dg/strlenopt-61.c
index 4f8e9c053e4..97790ac1fae 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-61.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-61.c
@@ -215,4 +215,4 @@  void test_ta2 (void)
 }
 
 /* { dg-final { scan-tree-dump-not "failure" "optimized" } }
-   { dg-final { scan-tree-dump-not "strlen" "gimple" } } */
+   { dg-final { scan-tree-dump-not "strlen2" "gimple" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-63.c b/gcc/testsuite/gcc.dg/strlenopt-63.c
new file mode 100644
index 00000000000..56d314e5d45
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strlenopt-63.c
@@ -0,0 +1,382 @@ 
+/* PR tree-optimization/83431 - Verify that snprintf (0, 0, "%s",
+   with an argument that's a conditional expression evaluates to
+   the expected result regardless of the order of the expression
+   operands.
+   { dg-do run }
+   { dg-options "-O2 -Wall" } */
+
+#include "strlenopt.h"
+
+#define A(expr)                                                 \
+  ((expr)                                                       \
+   ? (void)0                                                    \
+   : (__builtin_printf ("assertion failed on line %i: %s\n",    \
+                        __LINE__, #expr),                       \
+      __builtin_abort ()))
+
+const char gs0[] = "";
+const char gs3[] = "123";
+
+char gc;
+char ga5[7];
+
+struct S { char n, ma7[7], max[]; };
+
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_gs0_gs3_ga5_m1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? gs0 : 0 < i ? gs3 : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_gs0_gs3_ga5_0 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? gs0 : 0 < i ? gs3 : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_gs0_gs3_ga5_p1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? gs0 : 0 < i ? gs3 : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_gs0_ga5_gs3_m1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? gs0 : 0 < i ? ga5 : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_gs0_ga5_gs3_0 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? gs0 : 0 < i ? ga5 : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_gs0_ga5_gs3_p1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? gs0 : 0 < i ? ga5 : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_ga5_gs0_gs3_m1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? ga5 : 0 < i ? gs0 : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_ga5_gs0_gs3_0 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? ga5 : 0 < i ? gs0 : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_ga5_gs0_gs3_p1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? ga5 : 0 < i ? gs0 : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_ga5_gs3_gs0_m1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? ga5 : 0 < i ? gs3 : gs0;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_ga5_gs3_gs0_0 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? ga5 : 0 < i ? gs3 : gs0;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_ga5_gs3_gs0_p1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? ga5 : 0 < i ? gs3 : gs0;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_gs3_gs0_ga5_m1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? gs3 : 0 < i ? gs0 : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_gs3_gs0_ga5_0 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? gs3 : 0 < i ? gs0 : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+equal_4_gs3_gs0_ga5_p1 (int i)
+{
+  strcpy (ga5, "1234");
+  const char *p = i < 0 ? gs3 : 0 < i ? gs0 : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+
+/* Similar to the above but with memcpy creating a string at least
+   four characters long, and the address of the NUL character.  */
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_gc_gs3_ga5_m1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? &gc : 0 < i ? gs3 : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_gc_gs3_ga5_0 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? &gc : 0 < i ? gs3 : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_gc_gs3_ga5_p1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? &gc : 0 < i ? gs3 : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_gc_ga5_gs3_m1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? &gc : 0 < i ? ga5 : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_gc_ga5_gs3_0 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? &gc : 0 < i ? ga5 : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_gc_ga5_gs3_p1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? &gc : 0 < i ? ga5 : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_ga5_gc_gs3_m1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? ga5 : 0 < i ? &gc : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_ga5_gc_gs3_0 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? ga5 : 0 < i ? &gc : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_ga5_gc_gs3_p1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? ga5 : 0 < i ? &gc : gs3;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_ga5_gs3_gc_m1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? ga5 : 0 < i ? gs3 : &gc;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_ga5_gs3_gc_0 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? ga5 : 0 < i ? gs3 : &gc;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_ga5_gs3_gc_p1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? ga5 : 0 < i ? gs3 : &gc;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_gs3_gc_ga5_m1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? gs3 : 0 < i ? &gc : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 3);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_gs3_gc_ga5_0 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? gs3 : 0 < i ? &gc : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 4);
+}
+
+__attribute__ ((noclone, noinline, noipa)) void
+min_4_gs3_gc_ga5_p1 (int i)
+{
+  gc = 0;
+  memcpy (ga5, "1234", 4);
+  const char *p = i < 0 ? gs3 : 0 < i ? &gc : ga5;
+
+  A (snprintf (0, 0, "%s", p) == 0);
+}
+
+
+int main (void)
+{
+  equal_4_gs0_gs3_ga5_m1 (-1);
+  equal_4_gs0_gs3_ga5_0  ( 0);
+  equal_4_gs0_gs3_ga5_p1 (+1);
+
+  equal_4_gs0_ga5_gs3_m1 (-1);
+  equal_4_gs0_ga5_gs3_0  ( 0);
+  equal_4_gs0_ga5_gs3_p1 (+1);
+
+  equal_4_ga5_gs0_gs3_m1 (-1);
+  equal_4_ga5_gs0_gs3_0  ( 0);
+  equal_4_ga5_gs0_gs3_p1 (+1);
+
+  equal_4_ga5_gs3_gs0_m1 (-1);
+  equal_4_ga5_gs3_gs0_0  ( 0);
+  equal_4_ga5_gs3_gs0_p1 (+1);
+
+  equal_4_gs3_gs0_ga5_m1 (-1);
+  equal_4_gs3_gs0_ga5_0  ( 0);
+  equal_4_gs3_gs0_ga5_p1 (+1);
+
+  /* Same as aabove but with memcpy creating a string at least four
+     characters long.  */
+  memset (ga5, 0, sizeof ga5);
+  min_4_gc_gs3_ga5_m1 (-1);
+  memset (ga5, 0, sizeof ga5);
+  min_4_gc_gs3_ga5_0  ( 0);
+  memset (ga5, 0, sizeof ga5);
+  min_4_gc_gs3_ga5_p1 (+1);
+
+  memset (ga5, 0, sizeof ga5);
+  min_4_gc_ga5_gs3_m1 (-1);
+  memset (ga5, 0, sizeof ga5);
+  min_4_gc_ga5_gs3_0  ( 0);
+  memset (ga5, 0, sizeof ga5);
+  min_4_gc_ga5_gs3_p1 (+1);
+
+  memset (ga5, 0, sizeof ga5);
+  min_4_ga5_gc_gs3_m1 (-1);
+  memset (ga5, 0, sizeof ga5);
+  min_4_ga5_gc_gs3_0  ( 0);
+  memset (ga5, 0, sizeof ga5);
+  min_4_ga5_gc_gs3_p1 (+1);
+
+  memset (ga5, 0, sizeof ga5);
+  min_4_ga5_gs3_gc_m1 (-1);
+  memset (ga5, 0, sizeof ga5);
+  min_4_ga5_gs3_gc_0  ( 0);
+  memset (ga5, 0, sizeof ga5);
+  min_4_ga5_gs3_gc_p1 (+1);
+
+  memset (ga5, 0, sizeof ga5);
+  min_4_gs3_gc_ga5_m1 (-1);
+  memset (ga5, 0, sizeof ga5);
+  min_4_gs3_gc_ga5_0  ( 0);
+  memset (ga5, 0, sizeof ga5);
+  min_4_gs3_gc_ga5_p1 (+1);
+}
diff --git a/gcc/testsuite/gcc.dg/strlenopt-7.c b/gcc/testsuite/gcc.dg/strlenopt-7.c
index aa53d7e7525..7e27952dec6 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-7.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-7.c
@@ -40,12 +40,12 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "\\*r_\[0-9\]* = 0;" 1 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "\\*r_\[0-9\]* = 0;" 1 "strlen2" } } */
 /* { dg-final { scan-tree-dump-times "return 3;" 1 "optimized" } } */
 /* { dg-final { scan-tree-dump-times "return 0;" 2 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-8.c b/gcc/testsuite/gcc.dg/strlenopt-8.c
index 85c6d38a965..ed2215d73ba 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-8.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-8.c
@@ -47,10 +47,10 @@  main ()
    into and end up with a short typed load / store which strlenopt is not
    able to analyze.  */
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen" { xfail non_strict_align } } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen" { target { non_strict_align } } } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen" { target { ! non_strict_align } } } }  */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 0 "strlen2" { xfail non_strict_align } } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen2" { target { non_strict_align } } } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 4 "strlen2" { target { ! non_strict_align } } } }  */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-9.c b/gcc/testsuite/gcc.dg/strlenopt-9.c
index e8ff1023d71..613f45b2cc9 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-9.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-9.c
@@ -98,10 +98,10 @@  main ()
   return 0;
 }
 
-/* { dg-final { scan-tree-dump-times "strlen \\(" 5 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "memcpy \\(" 6 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen" } } */
-/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen" } } */
+/* { dg-final { scan-tree-dump-times "strlen \\(" 5 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "memcpy \\(" 6 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcpy \\(" 1 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strcat \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "strchr \\(" 0 "strlen2" } } */
+/* { dg-final { scan-tree-dump-times "stpcpy \\(" 0 "strlen2" } } */
 /* { dg-final { scan-tree-dump-times "return 4;" 1 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt.h b/gcc/testsuite/gcc.dg/strlenopt.h
index a4044fd28f5..52cada747b5 100644
--- a/gcc/testsuite/gcc.dg/strlenopt.h
+++ b/gcc/testsuite/gcc.dg/strlenopt.h
@@ -1,4 +1,4 @@ 
-/* This is a replacement of needed parts from stdlib.h and string.h
+/* This is a replacement of needed parts from <stdlib.h> and <string.h>
    for -foptimize-strlen testing, to ensure we are testing the builtins
    rather than whatever the OS has in its headers.  */
 
@@ -23,6 +23,9 @@  void *mempcpy (void *__restrict, const void *__restrict, size_t);
 char *stpcpy (char *__restrict, const char *__restrict);
 #endif
 
+int sprintf (char * __restrict, const char *__restrict, ...);
+int snprintf (char * __restrict, size_t, const char *__restrict, ...);
+
 #if defined(FORTIFY_SOURCE) && FORTIFY_SOURCE > 0 && __OPTIMIZE__
 # define bos(ptr) __builtin_object_size (ptr, FORTIFY_SOURCE > 0)
 # define bos0(ptr) __builtin_object_size (ptr, 0)
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-6.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-6.c
new file mode 100644
index 00000000000..d9e7ccd00ab
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-6.c
@@ -0,0 +1,145 @@ 
+/* Test to verify that snprintf can determine the length of a dynamically
+   constructed string argument and fold the result into a constant.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -fdump-tree-optimized" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+char* strcpy (char * restrict, const char * restrict);
+int sprintf (char * restrict, const char *restrict, ...);
+int snprintf (char * restrict, size_t, const char *restrict, ...);
+
+
+#define CONCAT(x, y) x ## y
+#define CAT(x, y) CONCAT (x, y)
+#define FAILNAME(name, counter)						\
+  CAT (CAT (CAT (call_ ## name ##_on_line_, __LINE__), _), counter)
+
+#define FAIL(name, counter) do {			\
+    extern void FAILNAME (name, counter) (void);	\
+    FAILNAME (name, counter)();				\
+  } while (0)
+
+/* Macro to emit a call to funcation named
+   call_in_true_branch_not_eliminated_on_line_NNN()
+   for each call that's expected to be eliminated.  The dg-final
+   scan-tree-dump-time directive at the bottom of the test verifies
+   that no such call appears in output.  */
+#define ELIM(expr)							\
+  if (!(expr)) FAIL (in_true_branch_not_eliminated, __COUNTER__); else (void)0
+
+#define ARGS(...) __VA_ARGS__
+
+#define T(expect, init, fmt, ...)			\
+  do {							\
+    char a[] = init;					\
+    ELIM (expect == snprintf (0, 0, fmt, __VA_ARGS__));	\
+  } while (0)
+
+/* Exercise a non-const local char array initialized by a string literal.  */
+void test_assign_string (void)
+{
+  T (0, "", "%s", a);
+  T (1, "1", "%s", a);
+  T (4, "1234", "%s", a);
+  T (5, "123", "s=%s", a);
+  T (5, "1234", "s=%s", a + 1);
+  T (2, "1234", "s=%s", a + 4);
+  T (5, "12345", "s=%s", &a[2]);
+  T (5, "123456", "s=%.*s", 3, &a[2]);
+}
+
+/* Exercise a non-const local char array initialized by an initializer
+   list.  */
+void test_assign_init_list (void)
+{
+  T (0, ARGS ({ 0 }), "%s", a);
+  T (1, ARGS ({ 1, 0 }), "%s", a);
+  T (3, ARGS ({ [3] = 0, [1] = 2, [0] = 1, [2] = 3 }), "%s", a);
+  T (3, ARGS ({ [3] = 0, [1] = 2, [0] = 1, [2] = 3, [4] = 0 }), "%s", a);
+  T (4, ARGS ({ 1, 2, 3, 4, 0 }), "%s", a);
+  T (5, ARGS ({ 1, 2, 3, 0 }), "s=%s", a);
+  T (5, ARGS ({ 1, 2, 3, 4, 0 }), "s=%s", a + 1);
+  T (2, ARGS ({ 1, 2, 3, 4, 0 }), "s=%s", a + 4);
+  T (5, ARGS ({ 1, 2, 3, 4, 5, 0 }), "s=%s", &a[2]);
+  T (5, ARGS ({ 1, 2, 3, 4, 5, 6, 0 }), "s=%.*s", 3, &a[2]);
+}
+
+#undef T
+#define T(expect, init, fmt, ...)			\
+  do {							\
+    struct { int n; char a[sizeof init]; }		\
+    s = { sizeof init, init };				\
+    ELIM (expect == snprintf (0, 0, fmt, __VA_ARGS__));	\
+  } while (0)
+
+/* Exercise a non-const local struct initialized by an initializer
+   list.  */
+void test_assign_aggregate (void)
+{
+  T (0, "", "%s", s.a);
+  T (1, "1", "%s", s.a);
+  T (4, "1234", "%s", s.a);
+  T (5, "123", "s=%s", s.a);
+  T (5, "1234", "s=%s", s.a + 1);
+  T (2, "1234", "s=%s", s.a + 4);
+  T (5, "12345", "s=%s", &s.a[2]);
+  T (5, "123456", "s=%.*s", 3, &s.a[2]);
+}
+
+
+#undef T
+#define T(expect, init, fmt, ...)			\
+  do {							\
+    char a[sizeof init];				\
+    strcpy (a, init);					\
+    ELIM (expect == snprintf (0, 0, fmt, __VA_ARGS__));	\
+  } while (0)
+
+/* Exercise a local char array initialized by a call to strcpy.  */
+void test_local_strcpy (void)
+{
+  T (0, "", "%s", a);
+  T (1, "1", "%s", a);
+  T (2, "12", "%s", a);
+  T (3, "123", "%s", a);
+  T (4, "1234", "%s", a);
+  T (5, "123", "s=%s", a);
+  T (5, "1234", "s=%s", a + 1);
+  T (2, "1234", "s=%s", a + 4);
+  T (5, "12345", "s=%s", &a[2]);
+  T (5, "123456", "s=%.*s", 3, &a[2]);
+}
+
+#if 0   /* Disabled and xfailed until PR 90662 is resolved.  */
+
+/* { dg-final { scan-tree-dump "test_vla_strcpy" "optimized" { xfail *-*-* } } } */
+
+#undef T
+#define T(expect, init, fmt, ...)			\
+  do {							\
+    char a[n];						\
+    strcpy (a, init);					\
+    ELIM (expect == snprintf (0, 0, fmt, __VA_ARGS__));	\
+  } while (0)
+
+/* Exercise a VLA initialized by a call to strcpy.  */
+void test_vla_strcpy (unsigned n)
+{
+  T (0, "", "%s", a);
+  T (1, "1", "%s", a);
+  T (2, "12", "%s", a);
+  T (3, "123", "%s", a);
+  T (4, "1234", "%s", a);
+  T (5, "123", "s=%s", a);
+  T (5, "1234", "s=%s", a + 1);
+  T (2, "1234", "s=%s", a + 4);
+  T (5, "12345", "s=%s", &a[2]);
+  T (5, "123456", "s=%.*s", 3, &a[2]);
+}
+
+#endif
+
+/* { dg-final { scan-tree-dump-times "printf" 0 "optimized" } }
+   { dg-final { scan-tree-dump-times "strlen" 0 "optimized" } }
+   { dg-final { scan-tree-dump-times "not_eliminated" 0 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c
new file mode 100644
index 00000000000..bf5072e955c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c
@@ -0,0 +1,152 @@ 
+/* Test to verify that snprintf can determine the correct range
+   of lengths of dynamically constructed string arguments.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -fdump-tree-optimized" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void* memcpy (void*, const void*, size_t);
+
+char* strcpy (char * restrict, const char * restrict);
+int snprintf (char * restrict, size_t, const char *restrict, ...);
+
+void sink (void*, ...);
+
+#define CONCAT(x, y) x ## y
+#define CAT(x, y) CONCAT (x, y)
+#define FAILNAME(name, counter)						\
+  CAT (CAT (CAT (call_ ## name ##_on_line_, __LINE__), _), counter)
+
+#define FAIL(name, counter) do {			\
+    extern void FAILNAME (name, counter) (void);	\
+    FAILNAME (name, counter)();				\
+  } while (0)
+
+/* Macro to emit a call to funcation named
+   call_in_true_branch_not_eliminated_on_line_NNN()
+   for each call that's expected to be eliminated.  The dg-final
+   scan-tree-dump-time directive at the bottom of the test verifies
+   that no such call appears in output.  */
+#define VERIFY_ELIM(expr)						\
+  if (!(expr)) FAIL (in_true_branch_not_eliminated, __COUNTER__); else (void)0
+
+/* Macro to emit a call to a function named
+     call_made_in_{true,false}_branch_on_line_NNN()
+   for each call that's expected to be retained.  The dg-final
+   scan-tree-dump-time directive at the bottom of the test verifies
+   that the expected number of both kinds of calls appears in output
+   (a pair for each line with the invocation of the KEEP() macro.  */
+#define VERIFY_KEEP(expr)			\
+  if (expr)					\
+    FAIL (made_in_true_branch, __COUNTER__);	\
+  else						\
+    FAIL (made_in_false_branch, __COUNTER__)
+
+#define ARGS(...) __VA_ARGS__
+
+/* Each test macro expands to a new function to get around bug 81776
+   - missing sprintf optimization due to pointer escape analysis.  */
+#define ELIM(expect, dst, init, fmt, ...)		\
+  void CAT (test_func_on_line_, __LINE__)(void)		\
+  {							\
+    memcpy (dst, init, sizeof (init) - 1);		\
+    const int res = snprintf (0, 0, fmt, __VA_ARGS__);	\
+    VERIFY_ELIM (expect res);				\
+  } typedef void dummy_typedef
+
+#define KEEP(expect, dst, init, fmt, ...)		\
+  void CAT (test_func_on_line_, __LINE__)(void)		\
+  {							\
+    memcpy (dst, init, sizeof (init) - 1);		\
+    const int ret = snprintf (0, 0, fmt, __VA_ARGS__);	\
+    VERIFY_KEEP (expect ret);				\
+  } typedef void dummy_typedef
+
+
+/* Verify that conditions involving snprintf calls with a string
+   of some minimum but otherwise unbounded length stored in an array
+   of unknown bound are not folded unless the format string itself
+   restricts the maximum.  The string could be longer than INT_MAX
+   making the snprintf call fail and return a negative value.  */
+
+extern char gax[];
+
+KEEP (1 <=, gax, "1",  "%s", gax);
+KEEP (2 <=, gax, "12", "%s", gax);
+KEEP (3 <=, gax, "123", "%s", gax);
+
+ELIM (3 ==, gax, "123", "%.3s", gax);
+ELIM (5 ==, gax, "123", "%.3s%.2s", gax, gax);
+
+
+/* Disabled.  The global pointer passed to memcpy as the destination
+   might point at itself, i.e., gptr == &gptr is a valid argument to
+   memcpy.
+
+extern char *gptr;
+
+KEEP (1 <=, gptr, "1",  "%s", gptr);
+KEEP (2 <=, gptr, "12", "%s", gptr);
+KEEP (3 <=, gptr, "123", "%s", gptr);
+
+ELIM (3 ==, gptr, "123", "%.3s", gptr);
+ELIM (5 ==, gptr, "123", "%.3s%.2s", gptr, gptr);
+
+*/
+
+/* Verify that conditions involving snprintf calls with a string
+   of some minimum but otherwise unbounded length stored in an array
+   of a known bound are folded.  The longest string that can be
+   stored in such arrays is bounded by the size of the array.  */
+
+extern char ga4[4];
+
+ELIM (0 <=, ga4, "\0",   "%s", ga4);
+ELIM (3 >=, ga4, "\0",   "%s", ga4);
+
+ELIM (1 <=, ga4, "1",  "%s", ga4);
+ELIM (0 <=, ga4, "1",  "%s", ga4 + 1);
+ELIM (0 <=, ga4, "1",  "%s", &ga4[1]);
+
+ELIM (3 >=, ga4, "1",  "%s", ga4);
+ELIM (2 >=, ga4, "1",  "%s", ga4 + 1);
+ELIM (2 >=, ga4, "1",  "%s", &ga4[1]);
+
+ELIM (2 <=, ga4, "12", "%s", ga4);
+ELIM (3 >=, ga4, "12", "%s", ga4);
+
+ELIM (3 <=, ga4, "123", "%s", ga4);
+ELIM (3 ==, ga4, "123", "%.3s", ga4);
+ELIM (5 ==, ga4, "123", "%.3s%.2s", ga4, ga4);
+
+/* Verify conditionals involving dynamically created strings of known
+   length stored in local arrays.  */
+
+#undef ELIM
+#define ELIM(expect, N1, N2, init1, init2, fmt, ...)	\
+  void CAT (test_func_on_line_, __LINE__)(int i)	\
+  {							\
+    char a1[N1], a2[N2];				\
+    memcpy (a1, init1, sizeof (init1) - 1);		\
+    memcpy (a2, init2, sizeof (init2) - 1);		\
+    const int res = snprintf (0, 0, fmt, __VA_ARGS__);	\
+    VERIFY_ELIM (expect res);				\
+  } typedef void dummy_typedef
+
+ELIM (0 ==, 2, 2, "\0", "\0",   "%s",         i ? a1 : a2);
+ELIM (2 ==, 2, 2, "\0", "\0",   "s=%s",       i ? a1 : a2);
+
+ELIM (1 ==, 2, 2, "a\0", "b\0", "%s",         i ? a1 : a2);
+ELIM (3 ==, 2, 2, "a\0", "b\0", "s=%s",       i ? a1 : a2);
+
+ELIM (2 ==, 3, 5, "ab\0", "cd\0", "%s",       i ? a1 : a2);
+ELIM (3 ==, 3, 5, "ab\0", "cd\0", "%3s",      i ? a1 : a2);
+ELIM (3 ==, 5, 5, "abcd\0", "efgh\0", "%.3s", i ? a1 : a2);
+
+ELIM (3 ==, 4, 1, "abc\0", "", "%s",          i ? a1 : "def");
+ELIM (4 ==, 1, 5, "", "efgh\0", "%s",         i ? "abcd" : a2);
+
+ELIM (4 ==, 5, 5, "abcd\0", "efgh\0", "%s",   i < 0 ? a1 : 0 < i ? a2 : "ijkl");
+
+/* { dg-final { scan-tree-dump-times "_not_eliminated" 0 "optimized" } }
+   { dg-final { scan-tree-dump-times "call_made_" 6 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-21.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-21.c
new file mode 100644
index 00000000000..41f932a41a8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-21.c
@@ -0,0 +1,94 @@ 
+/* PR tree-optimization/83431 -Wformat-truncation may incorrectly report
+   truncation
+   { dg-do compile }
+   { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern int snprintf (char*, size_t, const char*, ...);
+extern char* strcpy (char*, const char*);
+
+struct S
+{
+  char a9[9];
+  char a5[5];
+  int x;
+};
+
+
+void test_assign_nowarn (struct S* s)
+{
+  int i = 0;
+
+  {
+    char a9[9] = "1234";
+    snprintf (s[i].a5, sizeof (s[i].a5), "%s", a9);         /* { dg-bogus "\\\[-Wformat-truncation]" } */
+  }
+
+  {
+    ++i;
+    char a8[8] = "123";
+    snprintf (s[i].a5, sizeof (s[i].a5), "%s\n", a8);       /* { dg-bogus "\\\[-Wformat-truncation]" } */
+  }
+
+  {
+    ++i;
+    char a7[7] = "12";
+    snprintf (s[i].a5, sizeof (s[i].a5), "[%s]", a7);       /* { dg-bogus "\\\[-Wformat-truncation]" } */
+  }
+
+  {
+    ++i;
+    char a6[6] = "1";
+    snprintf (s[i].a5, sizeof (s[i].a5), "[%s]\n", a6);     /* { dg-bogus "\\\[-Wformat-truncation]" } */
+  }
+}
+
+
+void test_strcpy_nowarn (struct S* s)
+{
+  int i = 0;
+
+  strcpy (s[i].a9, "1234");
+  snprintf (s[i].a5, sizeof (s[i].a5), "%s", s[i].a9);
+
+  ++i;
+  strcpy (s[i].a9, "123");
+  snprintf (s[i].a5, sizeof (s[i].a5), "%s\n", s[i].a9);    /* { dg-bogus "\\\[-Wformat-truncation]" } */
+
+  ++i;
+  strcpy (s[i].a9, "12");
+  snprintf (s[i].a5, sizeof (s[i].a5), "[%s]", s[i].a9);    /* { dg-bogus "\\\[-Wformat-truncation]" } */
+
+  ++i;
+  strcpy (s[i].a9, "1");
+  snprintf (s[i].a5, sizeof (s[i].a5), "[%s]\n", s[i].a9);  /* { dg-bogus "\\\[-Wformat-truncation]" } */
+}
+
+
+void test_warn (struct S* s)
+{
+  int i = 0;
+  strcpy (s[i].a9, "12345678");
+  snprintf (s[i].a5, sizeof (s[i].a5), "%s", s[i].a9);      /* { dg-warning "'%s' directive output truncated writing 8 bytes into a region of size 5" } */
+
+  ++i;
+  strcpy (s[i].a9, "1234567");
+  snprintf (s[i].a5, sizeof (s[i].a5), "%s", s[i].a9);      /* { dg-warning "'%s' directive output truncated writing 7 bytes into a region of size 5" } */
+
+  ++i;
+  strcpy (s[i].a9, "123456");
+  snprintf (s[i].a5, sizeof (s[i].a5), "%s", s[i].a9);      /* { dg-warning "'%s' directive output truncated writing 6 bytes into a region of size 5" } */
+
+  ++i;
+  strcpy (s[i].a9, "12345");
+  snprintf (s[i].a5, sizeof (s[i].a5), "%s", s[i].a9);      /* { dg-warning "'snprintf' output truncated before the last format character" } */
+
+  ++i;
+  strcpy (s[i].a9, "1234");
+  snprintf (s[i].a5, sizeof (s[i].a5), "%s\n", s[i].a9);    /* { dg-warning "output truncated before the last format character" } */
+
+  ++i;
+  strcpy (s[i].a9, "123");
+  snprintf (s[i].a5, sizeof (s[i].a5), ">%s<", s[i].a9);    /* { dg-warning "output truncated before the last format character" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/dump-4.c b/gcc/testsuite/gcc.dg/tree-ssa/dump-4.c
new file mode 100644
index 00000000000..9377ed4f420
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/dump-4.c
@@ -0,0 +1,11 @@ 
+/* PR middle-end/87052 - STRING_CST printing incomplete in Gimple dumps
+   { dg-do compile }
+   { dg-options "-fdump-tree-original" } */
+
+void* f (char *d, int c)
+{
+  return __builtin_memchr ("1\0\0", c, 4);
+}
+
+/* Veriy the full string appears in the dump:
+  { dg-final { scan-tree-dump "\"1\\\\x00\\\\x00\"" "original" } } */
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index 98d8420d8cf..4af39aa2130 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
@@ -555,14 +565,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;
@@ -653,6 +668,167 @@  get_string_length (strinfo *si)
   return si->nonzero_chars;
 }
 
+/* Attempt to determine the length of the string SRC.  On success, store
+   the length in *PDATA and return true.  Otherwise, return false.  */
+
+static bool
+get_range_strlen_dynamic (tree src, c_strlen_data *pdata, bitmap *visited)
+{
+  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;
+
+	      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))
+		    {
+		      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)))
+			{
+			  pdata->maxlen = build_all_ones_cst (size_type_node);
+			  continue;
+			}
+
+		      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
+	    pdata->minlen = build_int_cst (size_type_node,
+					   si->nonzero_chars ? 1 : 0);
+
+	  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)
+	{
+	  wide_int min, max;
+	  value_range_kind rng = get_range_info (pdata->minlen, &min, &max);
+	  if (rng == VR_RANGE)
+	    {
+	      pdata->minlen = wide_int_to_tree (size_type_node, min);
+	      pdata->maxlen = wide_int_to_tree (size_type_node, max);
+	    }
+	}
+      else
+	{
+	  pdata->maxlen = pdata->minlen;
+	  pdata->maxbound = pdata->minlen;
+	}
+
+      return true;
+    }
+
+  return false;
+}
+
+/* Analogous to get_range_strlen but for dynamically created strings.
+   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.  ELTSIZE
+   is the expected size of the string element in bytes: 1 for char and
+   some power of 2 for wide characters.  */
+
+void
+get_range_strlen_dynamic (tree src, c_strlen_data *pdata)
+{
+  bitmap visited = NULL;
+
+  if (!get_range_strlen_dynamic (src, pdata, &visited))
+    {
+      /* 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.  */
 
@@ -3592,84 +3768,232 @@  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,
+			 evrp_range_analyzer &evrp)
+{
+  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, evrp);
+      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, evrp);
+      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,
+			 evrp_range_analyzer &evrp)
 {
   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 (!check_and_optimize_call (gsi, evrp))
+	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)
@@ -3681,124 +4005,16 @@  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))
-	{
-	  tree type = TREE_TYPE (lhs);
-	  if (TREE_CODE (type) == ARRAY_TYPE)
-	    type = TREE_TYPE (type);
-	  if (TREE_CODE (type) == INTEGER_TYPE
-	      && TYPE_MODE (type) == TYPE_MODE (char_type_node)
-	      && TYPE_PRECISION (type) == TYPE_PRECISION (char_type_node))
+      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))
+	{
+	  if (TREE_CODE (lhs_type) == ARRAY_TYPE)
+	    lhs_type = TREE_TYPE (lhs_type);
+	  if (TREE_CODE (lhs_type) == INTEGER_TYPE
+	      && TYPE_MODE (lhs_type) == TYPE_MODE (char_type_node)
+	      && TYPE_PRECISION (lhs_type) == TYPE_PRECISION (char_type_node))
 	    {
 	      if (! handle_char_store (gsi))
 		return false;
@@ -3873,12 +4089,17 @@  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.  */
+  evrp_range_analyzer evrp;
+
   /* Flag that will trigger TODO_cleanup_cfg to be returned in strlen
      execute function.  */
   bool m_cleanup_cfg;
@@ -3890,6 +4111,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)
@@ -3963,8 +4186,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))
+	gsi_next (&gsi);
+    }
 
   if (cleanup_eh && gimple_purge_dead_eh_edges (bb))
       m_cleanup_cfg = true;
@@ -3981,6 +4212,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);
@@ -4008,7 +4241,7 @@  const pass_data pass_data_strlen =
   "strlen", /* name */
   OPTGROUP_NONE, /* optinfo_flags */
   TV_TREE_STRLEN, /* tv_id */
-  ( PROP_cfg | PROP_ssa ), /* properties_required */
+  PROP_cfg, /* properties_required */
   0, /* properties_provided */
   0, /* properties_destroyed */
   0, /* todo_flags_start */
@@ -4017,20 +4250,50 @@  const pass_data pass_data_strlen =
 
 class pass_strlen : public gimple_opt_pass
 {
+  bool do_optimize;
+
 public:
   pass_strlen (gcc::context *ctxt)
-    : gimple_opt_pass (pass_data_strlen, ctxt)
+    : gimple_opt_pass (pass_data_strlen, ctxt),
+    do_optimize ()
   {}
 
   /* opt_pass methods: */
-  virtual bool gate (function *) { return flag_optimize_strlen != 0; }
+
+  opt_pass * clone () {
+    return new pass_strlen (m_ctxt);
+  }
+
+  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
+     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);
+}
+
 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> ();
@@ -4040,10 +4303,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 ();
@@ -4064,6 +4334,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;
 }
 
diff --git a/gcc/tree-ssa-strlen.h b/gcc/tree-ssa-strlen.h
index 0b68465eb2b..41bfcc20156 100644
--- a/gcc/tree-ssa-strlen.h
+++ b/gcc/tree-ssa-strlen.h
@@ -25,4 +25,11 @@  extern bool is_strlen_related_p (tree, tree);
 extern bool maybe_diag_stxncpy_trunc (gimple_stmt_iterator, tree, tree);
 extern tree set_strlen_range (tree, wide_int, tree = NULL_TREE);
 
+struct c_strlen_data;
+extern void get_range_strlen_dynamic (tree , c_strlen_data *);
+
+/* APIs internal to strlen pass.  Defined in in gimple-ssa-sprintf.c.  */
+class evrp_range_analyzer;
+extern bool handle_printf_call (gimple_stmt_iterator *, evrp_range_analyzer &);
+
 #endif   // GCC_TREE_SSA_STRLEN_H