diff mbox

relax -Wformat-overflow for precision ranges (PR 79275)

Message ID 236621e2-1755-be9f-0b44-0bc9c8356d85@gmail.com
State New
Headers show

Commit Message

Martin Sebor Jan. 30, 2017, 9:28 p.m. UTC
Bug 79275 - -Wformat-overflow false positive exceeding INT_MAX in
glibc sysdeps/posix/tempname.c points out a false positive found
during a Glibc build and caused by the checker using the upper
bound of a range of precisions in string directives with string
arguments of non-constant length.  The attached patch relaxes
the checker to use the lower bound instead when appropriate.

Martin

Comments

Jeff Law Jan. 31, 2017, 10:33 p.m. UTC | #1
On 01/30/2017 02:28 PM, Martin Sebor wrote:
> Bug 79275 - -Wformat-overflow false positive exceeding INT_MAX in
> glibc sysdeps/posix/tempname.c points out a false positive found
> during a Glibc build and caused by the checker using the upper
> bound of a range of precisions in string directives with string
> arguments of non-constant length.  The attached patch relaxes
> the checker to use the lower bound instead when appropriate.
>
> Martin
>
> gcc-79275.diff
>
>
> PR middle-end/79275 -  -Wformat-overflow false positive exceeding INT_MAX in glibc sysdeps/posix/tempname.c
>
> gcc/testsuite/ChangeLog:
>
> 	PR middle-end/79275
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-11.c: New test.
> 	* gcc.dg/tree-ssa/pr79275.c: New test.
>
> gcc/ChangeLog:
>
> 	PR middle-end/79275
> 	* gimple-ssa-sprintf.c (get_string_length): Set lower bound to zero.
> 	(format_string): Tighten up the range of output for non-constant
> 	strings and correct the expected range for wide non-constant strings.
My general inclination is to ask this to wait for gcc-8 as it is not a 
regression, but instead a false positive in a new warning.

However, if we see this triggering with any significant frequency, then 
we should reevaluate.  Getting Marek's build logs and grepping through 
them might guide us a bit on this...


I'm not sure what the rationale is for length of zero at level 1 and 
length of one at higher levels for unknown strings.  I guess I can 
kindof see the former, though I suspect if we looked at the actual 
strings, zero length would actually be uncommon.

For level 1 and above assuming a single character seems way too 
tolerant.  We're issuing a "may" warning, so ISTM we ought to be 
assuming a much larger length here.  I realize that makes a lot more 
noise for the warning, but doesn't that better reflect what may happen?




>
> diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
> index 8261a44..c0c0a5f 100644
> --- a/gcc/gimple-ssa-sprintf.c
> +++ b/gcc/gimple-ssa-sprintf.c
> @@ -1986,43 +1987,89 @@ format_string (const directive &dir, tree arg)
>      }
>    else
>      {
> -      /* For a '%s' and '%ls' directive with a non-constant string,
> -	 the minimum number of characters is the greater of WIDTH
> -	 and either 0 in mode 1 or the smaller of PRECISION and 1
> -	 in mode 2, and the maximum is PRECISION or -1 to disable
> -	 tracking.  */
> +      /* For a '%s' and '%ls' directive with a non-constant string (either
> +	 one of a number of strings of known length or an unknown string)
> +	 the minimum number of characters is lesser of PRECISION[0] and
> +	 the length of the shortest known string or zero, and the maximum
> +	 is the lessser of the length of the longest known string or
s/lessser/lesser/

Jeff
Jakub Jelinek Jan. 31, 2017, 10:42 p.m. UTC | #2
On Tue, Jan 31, 2017 at 03:33:04PM -0700, Jeff Law wrote:
> My general inclination is to ask this to wait for gcc-8 as it is not a
> regression, but instead a false positive in a new warning.

Well, as the warning is enabled by -Wall, the false positives in it are
regressions (while false negatives are not, as it is a new warning).

> However, if we see this triggering with any significant frequency, then we
> should reevaluate.  Getting Marek's build logs and grepping through them
> might guide us a bit on this...
> 
> 
> I'm not sure what the rationale is for length of zero at level 1 and length
> of one at higher levels for unknown strings.  I guess I can kindof see the
> former, though I suspect if we looked at the actual strings, zero length
> would actually be uncommon.
> 
> For level 1 and above assuming a single character seems way too tolerant.
> We're issuing a "may" warning, so ISTM we ought to be assuming a much larger
> length here.  I realize that makes a lot more noise for the warning, but
> doesn't that better reflect what may happen?

If the compiler doesn't know anything useful about the string lengths
(and that is the case when the value range is VARYING or just extremely
large), then using the maximum bounds is really not useful at all.

We would then have to complain about every "%s %s" because in theory that
would not fit into address space and similar.

	Jakub
Jeff Law Jan. 31, 2017, 10:55 p.m. UTC | #3
On 01/31/2017 03:42 PM, Jakub Jelinek wrote:
> On Tue, Jan 31, 2017 at 03:33:04PM -0700, Jeff Law wrote:
>> My general inclination is to ask this to wait for gcc-8 as it is not a
>> regression, but instead a false positive in a new warning.
>
> Well, as the warning is enabled by -Wall, the false positives in it are
> regressions (while false negatives are not, as it is a new warning).
I wouldn't necessarily call that a regression though. But I see your point.

>
>> However, if we see this triggering with any significant frequency, then we
>> should reevaluate.  Getting Marek's build logs and grepping through them
>> might guide us a bit on this...
>>
>>
>> I'm not sure what the rationale is for length of zero at level 1 and length
>> of one at higher levels for unknown strings.  I guess I can kindof see the
>> former, though I suspect if we looked at the actual strings, zero length
>> would actually be uncommon.
>>
>> For level 1 and above assuming a single character seems way too tolerant.
>> We're issuing a "may" warning, so ISTM we ought to be assuming a much larger
>> length here.  I realize that makes a lot more noise for the warning, but
>> doesn't that better reflect what may happen?
>
> If the compiler doesn't know anything useful about the string lengths
> (and that is the case when the value range is VARYING or just extremely
> large), then using the maximum bounds is really not useful at all.
>
> We would then have to complain about every "%s %s" because in theory that
> would not fit into address space and similar.
I'm well aware of that.  But ISTM the choice of "1" is so lenient that 
it makes tracking in those cases largely pointless -- at which point I 
have to ask why bother tracking at all when we've got an unconstrained 
string like that.

jeff
Joseph Myers Jan. 31, 2017, 10:59 p.m. UTC | #4
On Tue, 31 Jan 2017, Jeff Law wrote:

> My general inclination is to ask this to wait for gcc-8 as it is not a
> regression, but instead a false positive in a new warning.

I'd hope it would be possible for current releases of GCC and glibc to 
build with each other.  As this seems to be a case where the warning is 
clearly bogus (and started appearing during GCC stage 4), a fix in GCC 
seems more appropriate than disabling the warning for that code in glibc.
Martin Sebor Feb. 2, 2017, 12:40 a.m. UTC | #5
On 01/31/2017 03:33 PM, Jeff Law wrote:
> On 01/30/2017 02:28 PM, Martin Sebor wrote:
>> Bug 79275 - -Wformat-overflow false positive exceeding INT_MAX in
>> glibc sysdeps/posix/tempname.c points out a false positive found
>> during a Glibc build and caused by the checker using the upper
>> bound of a range of precisions in string directives with string
>> arguments of non-constant length.  The attached patch relaxes
>> the checker to use the lower bound instead when appropriate.
>>
>> Martin
>>
>> gcc-79275.diff
>>
>>
>> PR middle-end/79275 -  -Wformat-overflow false positive exceeding
>> INT_MAX in glibc sysdeps/posix/tempname.c
>>
>> gcc/testsuite/ChangeLog:
>>
>>     PR middle-end/79275
>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-11.c: New test.
>>     * gcc.dg/tree-ssa/pr79275.c: New test.
>>
>> gcc/ChangeLog:
>>
>>     PR middle-end/79275
>>     * gimple-ssa-sprintf.c (get_string_length): Set lower bound to zero.
>>     (format_string): Tighten up the range of output for non-constant
>>     strings and correct the expected range for wide non-constant strings.
> My general inclination is to ask this to wait for gcc-8 as it is not a
> regression, but instead a false positive in a new warning.

I would feel better if the patch were committed not just because
of the false positives but also because it corrects the range for
non-constant wide strings (i.e., for something like
snprintf (0, 0, "%ls", rand () ? L"\u03a6" : L"" ) trunk thinks
the output is at most 1 byte when in reality it's more like 2
bytes (U+03a6 is "\xCE\xA6" in UTF-8).  It's probably a pretty
rare case but still.

>
> However, if we see this triggering with any significant frequency, then
> we should reevaluate.  Getting Marek's build logs and grepping through
> them might guide us a bit on this...
>
>
> I'm not sure what the rationale is for length of zero at level 1 and
> length of one at higher levels for unknown strings.  I guess I can
> kindof see the former, though I suspect if we looked at the actual
> strings, zero length would actually be uncommon.
>
> For level 1 and above assuming a single character seems way too
> tolerant.  We're issuing a "may" warning, so ISTM we ought to be
> assuming a much larger length here.  I realize that makes a lot more
> noise for the warning, but doesn't that better reflect what may happen?

My rationale for zero and one for strings of unknown length was
to try to avoid false positives.  The checker does use the size
of the array when a string of unknown length points to one as
the likely length.  That's run into the expected pushback but
at least there is (what I consider) a defensible rationale for
it (the string could potentially be as long as the array,
otherwise why make the array that big?)  It also uses the length
of the longest of the strings an argument is known to point to,
on the same basis.

My biggest concern with being more aggressive than that (besides
the pushback) is that I can't think of a good function to compute
the size (it can't very well be a constant).

Martin

>
>> diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
>> index 8261a44..c0c0a5f 100644
>> --- a/gcc/gimple-ssa-sprintf.c
>> +++ b/gcc/gimple-ssa-sprintf.c
>> @@ -1986,43 +1987,89 @@ format_string (const directive &dir, tree arg)
>>      }
>>    else
>>      {
>> -      /* For a '%s' and '%ls' directive with a non-constant string,
>> -     the minimum number of characters is the greater of WIDTH
>> -     and either 0 in mode 1 or the smaller of PRECISION and 1
>> -     in mode 2, and the maximum is PRECISION or -1 to disable
>> -     tracking.  */
>> +      /* For a '%s' and '%ls' directive with a non-constant string
>> (either
>> +     one of a number of strings of known length or an unknown string)
>> +     the minimum number of characters is lesser of PRECISION[0] and
>> +     the length of the shortest known string or zero, and the maximum
>> +     is the lessser of the length of the longest known string or
> s/lessser/lesser/
>
> Jeff
Jeff Law Feb. 2, 2017, 4:40 p.m. UTC | #6
On 01/31/2017 03:59 PM, Joseph Myers wrote:
> On Tue, 31 Jan 2017, Jeff Law wrote:
>
>> My general inclination is to ask this to wait for gcc-8 as it is not a
>> regression, but instead a false positive in a new warning.
>
> I'd hope it would be possible for current releases of GCC and glibc to
> build with each other.  As this seems to be a case where the warning is
> clearly bogus (and started appearing during GCC stage 4), a fix in GCC
> seems more appropriate than disabling the warning for that code in glibc.
As I mentioned to Jakub, I see things differently, but I can certainly 
see both your and Jakub's point of view.

The irony here is I'm currently poking at 79095 which is a false 
positive in a new warning as well and marked as a regression.  One could 
make the same arguments on both sides about that BZ :-)

Given we've got two release managers view 79275 as a regression and one 
non-release-manager that does not, I won't push on the not-a-regression 
issue.  I'll add the regression marker and put the patch back in the queue.

jeff
Jeff Law Feb. 2, 2017, 4:54 p.m. UTC | #7
On 02/01/2017 05:40 PM, Martin Sebor wrote:
> On 01/31/2017 03:33 PM, Jeff Law wrote:
>> On 01/30/2017 02:28 PM, Martin Sebor wrote:
>>> Bug 79275 - -Wformat-overflow false positive exceeding INT_MAX in
>>> glibc sysdeps/posix/tempname.c points out a false positive found
>>> during a Glibc build and caused by the checker using the upper
>>> bound of a range of precisions in string directives with string
>>> arguments of non-constant length.  The attached patch relaxes
>>> the checker to use the lower bound instead when appropriate.
>>>
>>> Martin
>>>
>>> gcc-79275.diff
>>>
>>>
>>> PR middle-end/79275 -  -Wformat-overflow false positive exceeding
>>> INT_MAX in glibc sysdeps/posix/tempname.c
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>>     PR middle-end/79275
>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-11.c: New test.
>>>     * gcc.dg/tree-ssa/pr79275.c: New test.
>>>
>>> gcc/ChangeLog:
>>>
>>>     PR middle-end/79275
>>>     * gimple-ssa-sprintf.c (get_string_length): Set lower bound to zero.
>>>     (format_string): Tighten up the range of output for non-constant
>>>     strings and correct the expected range for wide non-constant
>>> strings.
>> My general inclination is to ask this to wait for gcc-8 as it is not a
>> regression, but instead a false positive in a new warning.
So as I mentioned in my message to Joseph, I'm going to go with Joseph & 
Jakub's view that this should be considered a regression.

> My rationale for zero and one for strings of unknown length was
> to try to avoid false positives.  The checker does use the size
> of the array when a string of unknown length points to one as
> the likely length.  That's run into the expected pushback but
> at least there is (what I consider) a defensible rationale for
> it (the string could potentially be as long as the array,
> otherwise why make the array that big?)  It also uses the length
> of the longest of the strings an argument is known to point to,
> on the same basis.
>
> My biggest concern with being more aggressive than that (besides
> the pushback) is that I can't think of a good function to compute
> the size (it can't very well be a constant).
Presumably the argument against simply giving up and not checking at all 
is that by assuming length 1, we can still check all the other arguments 
and perhaps still give a warning if the sprintf overflows when the 
unbound string is essentially ignored?

jeff
Martin Sebor Feb. 2, 2017, 5:13 p.m. UTC | #8
>>> My general inclination is to ask this to wait for gcc-8 as it is not a
>>> regression, but instead a false positive in a new warning.
> So as I mentioned in my message to Joseph, I'm going to go with Joseph &
> Jakub's view that this should be considered a regression.

Okay.  I'll wait for your approval of the patch then (with the fix
for the typo you pointed out).

>> My biggest concern with being more aggressive than that (besides
>> the pushback) is that I can't think of a good function to compute
>> the size (it can't very well be a constant).
> Presumably the argument against simply giving up and not checking at all
> is that by assuming length 1, we can still check all the other arguments
> and perhaps still give a warning if the sprintf overflows when the
> unbound string is essentially ignored?

That's right.  Since assuming the length of an unknown string is zero
is always safe, ignoring the rest of the format when one is found never
even crossed my mind.  There are other problems we can find if we keep
going that don't necessarily depend on our knowledge of the string
length.  (E.g., excessive widths and precisions, null string pointers,
or even unterminated character arrays if/when that is implemented,
etc.)

Martin
Jeff Law Feb. 2, 2017, 5:26 p.m. UTC | #9
On 01/30/2017 02:28 PM, Martin Sebor wrote:
> Bug 79275 - -Wformat-overflow false positive exceeding INT_MAX in
> glibc sysdeps/posix/tempname.c points out a false positive found
> during a Glibc build and caused by the checker using the upper
> bound of a range of precisions in string directives with string
> arguments of non-constant length.  The attached patch relaxes
> the checker to use the lower bound instead when appropriate.
>
> Martin
>
> gcc-79275.diff
>
>
> PR middle-end/79275 -  -Wformat-overflow false positive exceeding INT_MAX in glibc sysdeps/posix/tempname.c
>
> gcc/testsuite/ChangeLog:
>
> 	PR middle-end/79275
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-11.c: New test.
> 	* gcc.dg/tree-ssa/pr79275.c: New test.
>
> gcc/ChangeLog:
>
> 	PR middle-end/79275
> 	* gimple-ssa-sprintf.c (get_string_length): Set lower bound to zero.
> 	(format_string): Tighten up the range of output for non-constant
> 	strings and correct the expected range for wide non-constant strings.
Couple more nits.

First, I expect the patch won't apply as-is with the operand order 
fixes.  There'll be trivial changes you'll need to make for that.

Along the same lines, this patch would introduce a new operand order nit 
here:


> +	}
> +      else if (0 <= dir.prec[1])
> +	{

Please consider documenting how we handle strings with unknown lengths.


I don't think those warrant waiting for another review round.  Fix, 
bootstrap, test and install.

jeff
Martin Sebor Feb. 2, 2017, 6 p.m. UTC | #10
On 02/02/2017 10:26 AM, Jeff Law wrote:
> On 01/30/2017 02:28 PM, Martin Sebor wrote:
>> Bug 79275 - -Wformat-overflow false positive exceeding INT_MAX in
>> glibc sysdeps/posix/tempname.c points out a false positive found
>> during a Glibc build and caused by the checker using the upper
>> bound of a range of precisions in string directives with string
>> arguments of non-constant length.  The attached patch relaxes
>> the checker to use the lower bound instead when appropriate.
>>
>> Martin
>>
>> gcc-79275.diff
>>
>>
>> PR middle-end/79275 -  -Wformat-overflow false positive exceeding
>> INT_MAX in glibc sysdeps/posix/tempname.c
>>
>> gcc/testsuite/ChangeLog:
>>
>>     PR middle-end/79275
>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-11.c: New test.
>>     * gcc.dg/tree-ssa/pr79275.c: New test.
>>
>> gcc/ChangeLog:
>>
>>     PR middle-end/79275
>>     * gimple-ssa-sprintf.c (get_string_length): Set lower bound to zero.
>>     (format_string): Tighten up the range of output for non-constant
>>     strings and correct the expected range for wide non-constant strings.
> Couple more nits.
>
> First, I expect the patch won't apply as-is with the operand order
> fixes.  There'll be trivial changes you'll need to make for that.

I had to back out those changes to avoid the massive conflict when
applying this patch and before retesting it.  Then I reapplied those
changes by hand.

>
> Along the same lines, this patch would introduce a new operand order nit
> here:
>
>
>> +    }
>> +      else if (0 <= dir.prec[1])
>> +    {

In light of the hassle this has caused (above, plus with the big
patch earlier) I can't help but question the value in making these
changes.  Isn't the code just as easy to read this way as the other?

           else if (dir.prec[1] >= 0)

Wouldn't it be if the 0 were spelled ZERO?

I never even thought about it until you started pointing it out,
but my own personal (subconscious) bias is clear from how I write
the code.  I'm also not the only one.

FWIW, the example above may not be one of them but there are cases
when the constant on the left actually makes the code much easier
to read than the other way, such as:

   gcc/read-rtl-function.c:      if (0 == strncmp (desc, "orig:", 5))

or

   cp/decl.c:            if (1 == simple_cst_equal (TREE_PURPOSE (t1)

It seems to me that we should be able to write these expressions
the way that's natural to us and at the same time be able to
comfortably read them both ways.  As always, I fully support
consistency and following a coding style where it matters.  I
just don't think this does.

>
> Please consider documenting how we handle strings with unknown lengths.
>
>
> I don't think those warrant waiting for another review round.  Fix,
> bootstrap, test and install.

Will do.

Thanks
Martin
Marek Polacek Feb. 2, 2017, 6:09 p.m. UTC | #11
On Thu, Feb 02, 2017 at 11:00:44AM -0700, Martin Sebor wrote:
> On 02/02/2017 10:26 AM, Jeff Law wrote:
> > On 01/30/2017 02:28 PM, Martin Sebor wrote:
> > > Bug 79275 - -Wformat-overflow false positive exceeding INT_MAX in
> > > glibc sysdeps/posix/tempname.c points out a false positive found
> > > during a Glibc build and caused by the checker using the upper
> > > bound of a range of precisions in string directives with string
> > > arguments of non-constant length.  The attached patch relaxes
> > > the checker to use the lower bound instead when appropriate.
> > > 
> > > Martin
> > > 
> > > gcc-79275.diff
> > > 
> > > 
> > > PR middle-end/79275 -  -Wformat-overflow false positive exceeding
> > > INT_MAX in glibc sysdeps/posix/tempname.c
> > > 
> > > gcc/testsuite/ChangeLog:
> > > 
> > >     PR middle-end/79275
> > >     * gcc.dg/tree-ssa/builtin-sprintf-warn-11.c: New test.
> > >     * gcc.dg/tree-ssa/pr79275.c: New test.
> > > 
> > > gcc/ChangeLog:
> > > 
> > >     PR middle-end/79275
> > >     * gimple-ssa-sprintf.c (get_string_length): Set lower bound to zero.
> > >     (format_string): Tighten up the range of output for non-constant
> > >     strings and correct the expected range for wide non-constant strings.
> > Couple more nits.
> > 
> > First, I expect the patch won't apply as-is with the operand order
> > fixes.  There'll be trivial changes you'll need to make for that.
> 
> I had to back out those changes to avoid the massive conflict when
> applying this patch and before retesting it.  Then I reapplied those
> changes by hand.
> 
> > 
> > Along the same lines, this patch would introduce a new operand order nit
> > here:
> > 
> > 
> > > +    }
> > > +      else if (0 <= dir.prec[1])
> > > +    {
> 
> In light of the hassle this has caused (above, plus with the big
> patch earlier) I can't help but question the value in making these
> changes.  Isn't the code just as easy to read this way as the other?
> 
>           else if (dir.prec[1] >= 0)
> 
> Wouldn't it be if the 0 were spelled ZERO?
> 
> I never even thought about it until you started pointing it out,
> but my own personal (subconscious) bias is clear from how I write
> the code.  I'm also not the only one.
> 
> FWIW, the example above may not be one of them but there are cases
> when the constant on the left actually makes the code much easier
> to read than the other way, such as:
 
Certainly not for me.

>   gcc/read-rtl-function.c:      if (0 == strncmp (desc, "orig:", 5))
> 
> or
> 
>   cp/decl.c:            if (1 == simple_cst_equal (TREE_PURPOSE (t1)
> 
> It seems to me that we should be able to write these expressions
> the way that's natural to us and at the same time be able to
> comfortably read them both ways.  As always, I fully support
> consistency and following a coding style where it matters.  I
> just don't think this does.

I find these harder to read and they always give me a pause, especially with >= or
<=.  I'd say that 99% of the codebase uses "obj >= 0", so we should fix the
rest and be consistent.

	Marek
Jeff Law Feb. 2, 2017, 6:34 p.m. UTC | #12
On 02/02/2017 11:09 AM, Marek Polacek wrote:

>>
>> It seems to me that we should be able to write these expressions
>> the way that's natural to us and at the same time be able to
>> comfortably read them both ways.  As always, I fully support
>> consistency and following a coding style where it matters.  I
>> just don't think this does.
>
> I find these harder to read and they always give me a pause, especially with >= or
> <=.  I'd say that 99% of the codebase uses "obj >= 0", so we should fix the
> rest and be consistent.
Exactly.

I liked the CONST == VAR style because it makes it impossible to typo = 
instead of ==.  I wanted to see how it'd play out so I didn't initially 
call the nit out to be fixed.  But I'm finding that consistently I'm 
having to double-take on conditionals where the constant was first.

Personal style sometimes takes a back seat to project consistency.  And 
as Marek points out, the vast majority of conditions in GCC are written 
as variable op constant.

It's time to bring consistency into the sprintf checking bits.

Jeff
diff mbox

Patch

PR middle-end/79275 -  -Wformat-overflow false positive exceeding INT_MAX in glibc sysdeps/posix/tempname.c

gcc/testsuite/ChangeLog:

	PR middle-end/79275
	* gcc.dg/tree-ssa/builtin-sprintf-warn-11.c: New test.
	* gcc.dg/tree-ssa/pr79275.c: New test.

gcc/ChangeLog:

	PR middle-end/79275
	* gimple-ssa-sprintf.c (get_string_length): Set lower bound to zero.
	(format_string): Tighten up the range of output for non-constant
	strings and correct the expected range for wide non-constant strings.

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 8261a44..c0c0a5f 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -1832,10 +1832,11 @@  get_string_length (tree str)
 	}
       else
 	{
-	  /* When the upper bound is unknown (as assumed to be excessive)
+	  /* 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.  */
+	     the shortest string and reset the lower bound to zero.  */
 	  res.range.likely = res.range.min ? res.range.min : warn_level > 1;
+	  res.range.min = 0;
 	}
 
       res.range.unlikely = res.range.max;
@@ -1986,43 +1987,89 @@  format_string (const directive &dir, tree arg)
     }
   else
     {
-      /* For a '%s' and '%ls' directive with a non-constant string,
-	 the minimum number of characters is the greater of WIDTH
-	 and either 0 in mode 1 or the smaller of PRECISION and 1
-	 in mode 2, and the maximum is PRECISION or -1 to disable
-	 tracking.  */
+      /* For a '%s' and '%ls' directive with a non-constant string (either
+	 one of a number of strings of known length or an unknown string)
+	 the minimum number of characters is lesser of PRECISION[0] and
+	 the length of the shortest known string or zero, and the maximum
+	 is the lessser of the length of the longest known string or
+	 PTRDIFF_MAX and PRECISION[1].  The likely length is either
+	 the minimum at level 1 and the greater of the minimum and 1
+	 at level 2.  This result is adjust upward for width (if it's
+	 specified).  */
+
+      if (dir.modifier == FMT_LEN_l)
+	{
+	  /* A wide character converts to as few as zero bytes.  */
+	  slen.range.min = 0;
+	  if (slen.range.max < target_int_max ())
+	    slen.range.max *= target_mb_len_max ();
+
+	  if (slen.range.likely < target_int_max ())
+	    slen.range.likely *= 2;
+
+	  if (slen.range.likely < target_int_max ())
+	    slen.range.unlikely *= target_mb_len_max ();
+	}
+
+      res.range = slen.range;
 
       if (0 <= dir.prec[0])
 	{
+	  /* Adjust the minimum to zero if the string length is unknown,
+	     or at most the lower bound of the precision otherwise.  */
 	  if (slen.range.min >= target_int_max ())
-	    slen.range.min = 0;
+	    res.range.min = 0;
 	  else if ((unsigned HOST_WIDE_INT)dir.prec[0] < slen.range.min)
-	    {
-	      slen.range.min = dir.prec[0];
-	      slen.range.likely = slen.range.min;
-	    }
+	    res.range.min = dir.prec[0];
 
+	  /* Make both maxima no greater than the upper bound of precision.  */
 	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.max
 	      || slen.range.max >= target_int_max ())
 	    {
-	      slen.range.max = dir.prec[1];
-	      slen.range.likely = slen.range.max;
+	      res.range.max = dir.prec[1];
+	      res.range.unlikely = dir.prec[1];
 	    }
+
+	  /* If precision is constant, set the likely counter to the lesser
+	     of it and the maximum string length.  Otherwise, if the lower
+	     bound of precision is greater than zero, set the likely counter
+	     to the minimum.  Otherwise set it to zero or one based on
+	     the warning level.  */
+	  if (dir.prec[0] == dir.prec[1])
+	    res.range.likely
+	      = ((unsigned HOST_WIDE_INT)dir.prec[0] < slen.range.max
+		 ? dir.prec[0] : slen.range.max);
+	  else if (dir.prec[0] > 0)
+	    res.range.likely = res.range.min;
+	  else
+	    res.range.likely = warn_level > 1;
+	}
+      else if (0 <= dir.prec[1])
+	{
+	  res.range.min = 0;
+	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.max)
+	    res.range.max = dir.prec[1];
+	  res.range.likely = dir.prec[1] ? warn_level > 1 : 0;
 	}
       else if (slen.range.min >= target_int_max ())
 	{
-	  slen.range.min = 0;
-	  slen.range.max = HOST_WIDE_INT_MAX;
-	  /* At level one strings of unknown length are assumed to be
+	  res.range.min = 0;
+	  res.range.max = HOST_WIDE_INT_MAX;
+	  /* At level 1 strings of unknown length are assumed to be
 	     empty, while at level 1 they are assumed to be one byte
 	     long.  */
-	  slen.range.likely = warn_level > 1;
+	  res.range.likely = warn_level > 1;
+	}
+      else
+	{
+	  /* A string of unknown length unconstrained by precision is
+	     assumed to be empty at level 1 and just one character long
+	     at higher levels.  */
+	  if (res.range.likely >= target_int_max ())
+	    res.range.likely = warn_level > 1;
 	}
 
-      slen.range.unlikely = slen.range.max;
-
-      res.range = slen.range;
-      res.knownrange = slen.knownrange;
+      res.range.unlikely = res.range.max;
     }
 
   /* Bump up the byte counters if WIDTH is greater.  */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-11.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-11.c
new file mode 100644
index 0000000..b714c95
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-11.c
@@ -0,0 +1,323 @@ 
+/* PR middle-end/79275 - -Wformat-overflow false positive exceeding INT_MAX
+   in glibc sysdeps/posix/tempname.c
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wformat-overflow=1 -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__  size_t;
+typedef __WCHAR_TYPE__ wchar_t;
+
+#define INT_MAX __INT_MAX__
+#define INT_MIN (-INT_MAX - 1)
+
+/* When debugging, define LINE to the line number of the test case to exercise
+   and avoid exercising any of the others.  The buffer and objsize macros
+   below make use of LINE to avoid warnings for other lines.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+extern int int_value (void);
+extern size_t size_value (void);
+
+int int_range (int min, int max)
+{
+  int n = int_value ();
+  return n < min || max < n ? min : n;
+}
+
+size_t size_range (size_t min, size_t max)
+{
+  size_t n = size_value ();
+  return n < min || max < n ? min : n;
+}
+
+void sink (char*, char*);
+
+int dummy_sprintf (char*, const char*, ...);
+
+char buffer [256];
+extern char *ptr;
+
+const char s0[] = "";
+const char s1[] = "1";
+const char s2[] = "12";
+const char s3[] = "123";
+const char s4[] = "1234";
+const char s5[] = "12345";
+const char s6[] = "123456";
+const char s7[] = "1234567";
+const char s8[] = "12345678";
+const char s9[] = "123456789";
+extern const char sx[];
+extern const char sy[];
+
+/* Wide string literals outside the ASCII range to avoid assumptions
+   about the number of narrow characters they might convert to beyond
+   up to 6 bytes each (the maximum for UTF-8 not exceeded by any known
+   encoding).  */
+const wchar_t ws0[] = L"";
+const wchar_t ws1[] = L"\u1111";
+const wchar_t ws2[] = L"\u1111\u2222";
+const wchar_t ws3[] = L"\u1111\u2222\u3333";
+const wchar_t ws4[] = L"\u1111\u2222\u3333\u4444";
+const wchar_t ws5[] = L"\u1111\u2222\u3333\u4444\u5555";
+const wchar_t ws6[] = L"\u1111\u2222\u3333\u4444\u5555\u6666";
+const wchar_t ws7[] = L"\u1111\u2222\u3333\u4444\u5555\u6666\u7777";
+const wchar_t ws8[] =
+  L"\u1111\u2222\u3333\u4444\u5555\u6666\u7777\u8888";
+const wchar_t ws9[] =
+  L"\u1111\u2222\u3333\u4444\u5555\u6666\u7777\u8888\u9999";
+extern const wchar_t wsx[];
+extern const wchar_t wsy[];
+
+static const int imin = INT_MIN;
+static const int imax = INT_MAX;
+
+/* Evaluate to an array of SIZE characters when non-negative, or to
+   a pointer to an unknown object otherwise.  */
+#define buffer(size)					\
+  ((0 <= size) ? buffer + sizeof buffer - (size) : ptr)
+
+/* Helper to expand function to either __builtin_f or dummy_f to
+   make debugging GCC easy.  */
+#define FUNC(f)							\
+  ((!LINE || LINE == __LINE__) ? __builtin_ ## f : dummy_ ## f)
+
+/* Macro to verify that calls to __builtin_sprintf (i.e., with no size
+   argument) issue diagnostics by correctly determining the size of
+   the destination buffer.  */
+#define T(size, ...)						\
+  (FUNC (sprintf) (buffer (size),  __VA_ARGS__),		\
+   sink (buffer, ptr))
+
+/* Return a value in the range [MIN, MAX].  */
+#define IR(min, max)  int_range (min, max)
+
+/* Return a string whose length is in the range [MIN, MAX] where
+   both MIN and MAX must be digits in the range [0, 9].  */
+#define SR(min, max)  (int_value () < 0 ? s##min : s##max)
+
+/* Return a wide string whose length is in the range [MIN, MAX] where
+   both MIN and MAX must be digits in the range [0, 9].  */
+#define WR(min, max)  (int_value () < 0 ? ws##min : ws##max)
+
+void test_narrow_string_with_precision (void)
+{
+  T (-1, "%.*s", IR ( 0,  1), SR (0, 1));
+  T (-1, "%.*s", IR ( 0,  1), SR (0, 2));
+  T (-1, "%.*s", IR ( 0,  1), SR (0, 3));
+  T (-1, "%.*s", IR ( 0,  1), SR (0, 4));
+  T (-1, "%.*s", IR ( 0,  1), SR (0, 9));
+  T (-1, "%.*s", IR ( 0,  2), SR (0, 9));
+  T (-1, "%.*s", IR ( 0,  3), SR (0, 9));
+  T (-1, "%.*s", IR ( 0,  4), SR (0, 9));
+  T (-1, "%.*s", IR ( 0,  9), SR (0, 9));
+  T (-1, "%.*s", IR ( 0, 99), SR (0, 9));
+  T (-1, "%.*s", IR ( 0, 99), SR (0, x));
+  T (-1, "%.*s", IR ( 0, 99), SR (1, x));
+  T (-1, "%.*s", IR ( 0, 99), SR (x, 1));
+  T (-1, "%.*s", IR ( 0, 99), SR (x, 9));
+
+  T (-1, "%.*s", IR (imax / 3, imax / 2), SR (x, y));
+
+  /* Non-constant zero length string.  */
+  T ( 0, "%.*s", IR (imin, -1), SR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*s", IR (imin,  0), SR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*s", IR (-1,    0), SR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*s", IR (-1,    1), SR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*s", IR (-1,   99), SR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+
+  /* String with length between 0 and 1 character.  */
+  T ( 0, "%.*s", IR (imin, -1), SR (0, 1));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR (imin, 0),  SR (0, 1));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*s", IR (-2, -1),   SR (0, 1));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR (-2,  0),   SR (0, 1));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*s", IR ( 0,  1),   SR (0, 1));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR ( 0,  2),   SR (0, 1));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR ( 0, 99),   SR (0, 1));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR ( 0, imax), SR (0, 1));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR ( 1, imax), SR (0, 1));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR ( 9, imax), SR (0, 1));   /* { dg-warning "writing up to 1 byte" } */
+
+  /* String with length between 2 and 3 characters.  */
+  T ( 0, "%.*s", IR (imin, -1), SR (2, 3));   /* { dg-warning "writing between 2 and 3 bytes" } */
+  T ( 0, "%.*s", IR (imin, 0),  SR (2, 3));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*s", IR (-2, -1),   SR (2, 3));   /* { dg-warning "writing between 2 and 3 bytes" } */
+  T ( 0, "%.*s", IR (-2,  0),   SR (2, 3));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*s", IR (-2,  1),   SR (2, 3));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR ( 0,  1),   SR (2, 3));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR ( 0,  2),   SR (2, 3));   /* { dg-warning "writing up to 2 bytes" } */
+  T ( 0, "%.*s", IR ( 0, 99),   SR (2, 3));   /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%.*s", IR ( 0, imax), SR (2, 3));   /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%.*s", IR ( 1, 99),   SR (2, 3));   /* { dg-warning "writing between 1 and 3 bytes" } */
+  T ( 0, "%.*s", IR ( 9, 99),   SR (2, 3));   /* { dg-warning "writing between 2 and 3 bytes" } */
+
+  T ( 0, "%.*s", IR ( 0,  1),   SR (0, 9));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*s", IR ( 0,  2),   SR (0, 9));   /* { dg-warning "writing up to 2 bytes" } */
+  T ( 0, "%.*s", IR ( 0,  9),   SR (0, 9));   /* { dg-warning "writing up to 9 bytes" } */
+  T ( 0, "%.*s", IR ( 0, 79),   SR (0, 9));   /* { dg-warning "writing up to 9 bytes" } */
+  T ( 0, "%.*s", IR ( 1,  2),   SR (0, 9));   /* { dg-warning "writing up to 2 bytes" } */
+  T ( 0, "%.*s", IR ( 2,  3),   SR (0, 9));   /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%.*s", IR ( 7, 13),   SR (0, 9));   /* { dg-warning "writing up to 9 bytes" } */
+
+  /* String between N and unknown number of characters long.  */
+  T ( 0, "%.*s", IR (imin, -1), SR (0, x));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*s", IR (imin, -1), SR (1, x));   /* { dg-warning "writing likely 1 or more bytes" } */
+  T ( 1, "%.*s", IR (imin, -1), SR (1, x));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*s", IR (imin, -1), SR (8, x));   /* { dg-warning "writing likely 8 or more bytes" } */
+  T ( 1, "%.*s", IR (imin, -1), SR (x, 9));   /* { dg-warning "writing likely 9 or more bytes" } */
+
+  /* Unknown strings.  */
+  T ( 1, "%.*s", IR (imin, -1), SR (x, y));
+  T ( 1, "%.*s", IR (imin,  0), SR (x, y));
+  T ( 1, "%.*s", IR ( -99,  1), SR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*s", IR (  -2,  2), SR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*s", IR (  -1, 99), SR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*s", IR (   0, 99), SR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*s", IR (   1, 99), SR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*s", IR (   9, 99), SR (x, y));   /* { dg-warning "may write a terminating nul" } */
+}
+
+void test_narrow_string_with_width_and_precision (void)
+{
+  T (-1, "%*.*s", IR ( 0,  1), IR ( 0,  1), SR (0, 1));
+  T (-1, "%*.*s", IR ( 0,  1), IR ( 0,  1), SR (0, 2));
+  T (-1, "%*.*s", IR ( 0,  1), IR ( 0,  1), SR (0, 3));
+  T (-1, "%*.*s", IR ( 0,  1), IR ( 0,  1), SR (0, 4));
+  T (-1, "%*.*s", IR ( 0,  1), IR ( 0,  1), SR (0, 9));
+  T (-1, "%*.*s", IR ( 0,  2), IR ( 0,  2), SR (0, 9));
+  T (-1, "%*.*s", IR ( 0,  3), IR ( 0,  3), SR (0, 9));
+  T (-1, "%*.*s", IR ( 0,  4), IR ( 0,  4), SR (0, 9));
+  T (-1, "%*.*s", IR ( 0,  9), IR ( 0,  9), SR (0, 9));
+  T (-1, "%*.*s", IR ( 0, 99), IR ( 0, 99), SR (0, 9));
+  T (-1, "%*.*s", IR ( 0, 99), IR ( 0, 99), SR (0, x));
+  T (-1, "%*.*s", IR ( 0, 99), IR ( 0, 99), SR (1, x));
+  T (-1, "%*.*s", IR ( 0, 99), IR ( 0, 99), SR (x, 1));
+  T (-1, "%*.*s", IR ( 0, 99), IR ( 0, 99), SR (x, 9));
+  T (-1, "%*.*s", IR (12, 34), IR (45, 67), SR (x, 9));
+  T (-1, "%*.*s", IR (12, 34), IR (45, 67), SR (x, y));
+
+  T (-1, "%*.*s", IR (imax / 5, imax / 4), IR (imax / 3, imax / 2), SR (x, y));
+
+  T (-1, "%*.*s %*.*s",
+     IR (imax / 9, imax / 8), IR (imax / 7, imax / 6), SR (x, y),
+     IR (imax / 5, imax / 4), IR (imax / 3, imax / 2), SR (x, y));
+
+  /* The two directives below combined convert to [INT_MAX, INT_MAX + 1].
+     Since the lower end of the range doesn't exceed INT_MAX no warning
+     is expected.  */
+  T (-1, "%*.*s%*.*s",
+     IR (imax - 5, imax - 3), IR (1, 2), SR (x, y),
+     IR (       5,        6), IR (3, 4), SR (x, y));
+
+  /* The three directives below (the two %s plus the space in between)
+     combined convert to [INT_MAX + 1, INT_MAX + 2].  Since the lower
+     end of the range exceeds INT_MAX a warning is expected.  */
+  T (-1, "%*.*s %*.*s",                                     /* { dg-warning "INT_MAX" } */
+     IR (imax - 5, imax - 3), IR (1, 2), SR (x, y),
+     IR (       5,        6), IR (3, 4), SR (x, y));
+
+   /* Non-constant zero length string.  */
+  T ( 0, "%*.*s", IR ( 0, 1), IR (imin, -1), SR (0, 0));    /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%*.*s", IR ( 0, 2), IR (imin, -1), SR (0, 0));    /* { dg-warning "writing up to 2 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (imin, -1), SR (0, 0));    /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  1), SR (0, 0));    /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  1), SR (0, 1));    /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  2), SR (0, 1));    /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  3), SR (0, 1));    /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  1), SR (3, 5));    /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  2), SR (3, 5));    /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  3), SR (3, 5));    /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  4), SR (3, 5));    /* { dg-warning "writing up to 4 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  5), SR (3, 5));    /* { dg-warning "writing up to 5 bytes" } */
+  T ( 0, "%*.*s", IR ( 0, 3), IR (   0,  6), SR (3, 5));    /* { dg-warning "writing up to 5 bytes" } */
+
+  T ( 0, "%*.*s", IR ( 1, 2), IR (   0,  1), SR (3, 5));    /* { dg-warning "writing between 1 and 2 bytes" } */
+  T ( 0, "%*.*s", IR ( 1, 2), IR (   0,  2), SR (3, 5));    /* { dg-warning "writing between 1 and 2 bytes" } */
+  T ( 0, "%*.*s", IR ( 1, 2), IR (   0,  3), SR (3, 5));    /* { dg-warning "writing between 1 and 3 bytes" } */
+  T ( 0, "%*.*s", IR ( 1, 2), IR (   0,  4), SR (3, 5));    /* { dg-warning "writing between 1 and 4 bytes" } */
+  T ( 0, "%*.*s", IR ( 1, 2), IR (   0,  5), SR (3, 5));    /* { dg-warning "writing between 1 and 5 bytes" } */
+  T ( 0, "%*.*s", IR ( 1, 2), IR (   0,  6), SR (3, 5));    /* { dg-warning "writing between 1 and 5 bytes" } */
+  T ( 0, "%*.*s", IR ( 2, 3), IR (   0,  6), SR (3, 5));    /* { dg-warning "writing between 2 and 5 bytes" } */
+  T ( 0, "%*.*s", IR ( 2, 3), IR (   1,  6), SR (3, 5));    /* { dg-warning "writing between 2 and 5 bytes" } */
+  T ( 0, "%*.*s", IR ( 2, 3), IR (   2,  6), SR (3, 5));    /* { dg-warning "writing between 2 and 5 bytes" } */
+  T ( 0, "%*.*s", IR ( 2, 3), IR (   3,  6), SR (3, 5));    /* { dg-warning "writing between 3 and 5 bytes" } */
+  T ( 0, "%*.*s", IR ( 2, 3), IR (   4,  6), SR (3, 5));    /* { dg-warning "writing between 3 and 5 bytes" } */
+  T ( 0, "%*.*s", IR ( 2, 3), IR (   5,  6), SR (3, 5));    /* { dg-warning "writing between 3 and 5 bytes" } */
+}
+
+void test_wide_string (void)
+{
+  T (-1, "%.*ls", IR ( 0,  1), WR (0, 1));
+  T (-1, "%.*ls", IR ( 0,  1), WR (0, 2));
+  T (-1, "%.*ls", IR ( 0,  1), WR (0, 3));
+  T (-1, "%.*ls", IR ( 0,  1), WR (0, 4));
+  T (-1, "%.*ls", IR ( 0,  1), WR (0, 9));
+  T (-1, "%.*ls", IR ( 0,  2), WR (0, 9));
+  T (-1, "%.*ls", IR ( 0,  3), WR (0, 9));
+  T (-1, "%.*ls", IR ( 0,  4), WR (0, 9));
+  T (-1, "%.*ls", IR ( 0,  9), WR (0, 9));
+  T (-1, "%.*ls", IR ( 0, 99), WR (0, 9));
+  T (-1, "%.*ls", IR ( 0, 99), WR (0, x));
+  T (-1, "%.*ls", IR ( 0, 99), WR (1, x));
+  T (-1, "%.*ls", IR ( 0, 99), WR (x, 1));
+  T (-1, "%.*ls", IR ( 0, 99), WR (x, 9));
+
+   /* Non-constant zero length string.  */
+  T ( 0, "%.*ls", IR (imin, -1), WR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*ls", IR (imin,  0), WR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*ls", IR (-1,    0), WR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*ls", IR (-1,    1), WR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*ls", IR (-1,   99), WR (0, 0));   /* { dg-warning "writing a terminating nul" } */
+
+  /* String with length between 0 and 1 character.  */
+  T ( 0, "%.*ls", IR (imin, -1), WR (0, 1));   /* { dg-warning "writing up to 6 bytes" } */
+  T ( 0, "%.*ls", IR (imin, 0),  WR (0, 1));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*ls", IR (-2, -1),   WR (0, 1));   /* { dg-warning "writing up to 6 bytes" } */
+  T ( 0, "%.*ls", IR (-2,  0),   WR (0, 1));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*ls", IR ( 0,  1),   WR (0, 1));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*ls", IR ( 0,  2),   WR (0, 1));   /* { dg-warning "writing up to 2 bytes" } */
+  T ( 0, "%.*ls", IR ( 0, 99),   WR (0, 1));   /* { dg-warning "writing up to 6 bytes" } */
+  T ( 0, "%.*ls", IR ( 0, imax), WR (0, 1));   /* { dg-warning "writing up to 6 bytes" } */
+  T ( 0, "%.*ls", IR ( 1, imax), WR (0, 1));   /* { dg-warning "writing up to 6 bytes" } */
+  T ( 0, "%.*ls", IR ( 9, imax), WR (0, 1));   /* { dg-warning "writing up to 6 bytes" } */
+
+  /* String with length between 2 and 3 characters.  */
+  T ( 0, "%.*ls", IR (imin, -1), WR (2, 3));   /* { dg-warning "writing up to 18 bytes" } */
+  T ( 0, "%.*ls", IR (imin, 0),  WR (2, 3));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*ls", IR (-2, -1),   WR (2, 3));   /* { dg-warning "writing up to 18 bytes" } */
+  T ( 0, "%.*ls", IR (-2,  0),   WR (2, 3));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*ls", IR (-2,  1),   WR (2, 3));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*ls", IR ( 0,  1),   WR (2, 3));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*ls", IR ( 0,  2),   WR (2, 3));   /* { dg-warning "writing up to 2 bytes" } */
+  T ( 0, "%.*ls", IR ( 0, 99),   WR (2, 3));   /* { dg-warning "writing up to 18 bytes" } */
+  T ( 0, "%.*ls", IR ( 0, imax), WR (2, 3));   /* { dg-warning "writing up to 18 bytes" } */
+  T ( 0, "%.*ls", IR ( 1, 99),   WR (2, 3));   /* { dg-warning "writing up to 18 bytes" } */
+  T ( 0, "%.*ls", IR ( 9, 99),   WR (2, 3));   /* { dg-warning "writing up to 18 bytes" } */
+
+  T ( 0, "%.*ls", IR ( 0,  1),   WR (0, 9));   /* { dg-warning "writing up to 1 byte" } */
+  T ( 0, "%.*ls", IR ( 0,  2),   WR (0, 9));   /* { dg-warning "writing up to 2 bytes" } */
+  T ( 0, "%.*ls", IR ( 0,  9),   WR (0, 9));   /* { dg-warning "writing up to 9 bytes" } */
+  T ( 0, "%.*ls", IR ( 0, 53),   WR (0, 9));   /* { dg-warning "writing up to 53 bytes" } */
+  T ( 0, "%.*ls", IR ( 0, 55),   WR (0, 9));   /* { dg-warning "writing up to 54 bytes" } */
+  T ( 0, "%.*ls", IR ( 1,  2),   WR (0, 9));   /* { dg-warning "writing up to 2 bytes" } */
+  T ( 0, "%.*ls", IR ( 2,  3),   WR (0, 9));   /* { dg-warning "writing up to 3 bytes" } */
+  T ( 0, "%.*ls", IR ( 7, 13),   WR (0, 9));   /* { dg-warning "writing up to 13 bytes" } */
+
+  /* String between N and unknown number of characters long.  */
+  T ( 0, "%.*ls", IR (imin, -1), WR (0, x));   /* { dg-warning "writing a terminating nul" } */
+  T ( 0, "%.*ls", IR (imin, -1), WR (1, x));   /* { dg-warning "writing likely 2 or more bytes" } */
+  T ( 1, "%.*ls", IR (imin, -1), WR (1, x));   /* { dg-warning "writing likely 2 or more bytes" } */
+  T ( 1, "%.*ls", IR (imin, -1), WR (8, x));   /* { dg-warning "writing likely 16 or more bytes" } */
+  T ( 1, "%.*ls", IR (imin, -1), WR (x, 9));   /* { dg-warning "writing likely 18 or more bytes" } */
+
+  /* Unknown strings.  */
+  T ( 1, "%.*ls", IR (imin, -1), WR (x, y));
+  T ( 1, "%.*ls", IR (imin,  0), WR (x, y));
+  T ( 1, "%.*ls", IR ( -99,  1), WR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*ls", IR (  -2,  2), WR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*ls", IR (  -1, 99), WR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*ls", IR (   0, 99), WR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*ls", IR (   1, 99), WR (x, y));   /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*ls", IR (   9, 99), WR (x, y));   /* { dg-warning "may write a terminating nul" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr79275.c b/gcc/testsuite/gcc.dg/tree-ssa/pr79275.c
new file mode 100644
index 0000000..d314524
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr79275.c
@@ -0,0 +1,14 @@ 
+/* PR middle-end/79275 - -Wformat-overflow false positive exceeding INT_MAX
+   in glibc sysdeps/posix/tempname.c
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wformat-overflow=1 -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void f (char *dst, size_t n, const char *s)
+{
+  if (n < 2 || __INT_MAX__ - 2 < n)
+    n = 2;
+
+  __builtin_sprintf (dst, "%.*s %.*s", (int)n, s, (int)n, s);   /* { dg-bogus "INT_MAX" } */
+}