diff mbox

fix outstanding -Wformat-length failures (pr77735 et al.)

Message ID 3f12a02e-fd7f-8b58-2558-824ab0c0fca0@gmail.com
State New
Headers show

Commit Message

Martin Sebor Oct. 2, 2016, 8:10 p.m. UTC
The attached patch fixes a number of outstanding test failures
and ILP32-related bugs in the gimple-ssa-sprintf pattch pointed
out in bug 77676 and 77735).  The patch also fixes c_strlen to
correctly handle wide strings (previously it accepted them but
treated them as nul-terminated byte sequences), and adjusts the
handling of "%a" to avoid assuming a specific number of decimal
digits (this is likely a defect in C11 that I'm pursuing with
WG14).

Tested on powerpc64le, i386, and x86_64.

There is one outstanding failure in the builtin-sprintf-warn-1.c
test on powerpc64le that looks like it might be due to the
printf_pointer_format target hook not having been set up entirely
correctly.  I'll look into that separately, along with pr77819.

Martin

Comments

Martin Sebor Oct. 10, 2016, 11:46 p.m. UTC | #1
I'm looking for a review of the patch below:

   https://gcc.gnu.org/ml/gcc-patches/2016-10/msg00043.html

The patch should clean up the remaining test suite failures on
ILP32 targets and also fixes up some remaining issues in the
gimple-ssa-sprintf pass that stand in the way of re-enabling
the printf-return-value optimization.

I'm traveling next week so I'm hoping to enable the optimization
shortly after this patch goes in so that if there's any fallout
from it I can fix it before I leave.

Thanks
Martin

On 10/02/2016 02:10 PM, Martin Sebor wrote:
> The attached patch fixes a number of outstanding test failures
> and ILP32-related bugs in the gimple-ssa-sprintf pattch pointed
> out in bug 77676 and 77735).  The patch also fixes c_strlen to
> correctly handle wide strings (previously it accepted them but
> treated them as nul-terminated byte sequences), and adjusts the
> handling of "%a" to avoid assuming a specific number of decimal
> digits (this is likely a defect in C11 that I'm pursuing with
> WG14).
>
> Tested on powerpc64le, i386, and x86_64.
>
> There is one outstanding failure in the builtin-sprintf-warn-1.c
> test on powerpc64le that looks like it might be due to the
> printf_pointer_format target hook not having been set up entirely
> correctly.  I'll look into that separately, along with pr77819.
>
> Martin
Martin Sebor Oct. 17, 2016, 4:39 p.m. UTC | #2
I'm still looking for a review of the patch below:

   https://gcc.gnu.org/ml/gcc-patches/2016-10/msg00043.html

>
> The patch should clean up the remaining test suite failures on
> ILP32 targets and also fixes up some remaining issues in the
> gimple-ssa-sprintf pass that stand in the way of re-enabling
> the printf-return-value optimization.
>
> I'm traveling next week so I'm hoping to enable the optimization
> shortly after this patch goes in so that if there's any fallout
> from it I can fix it before I leave.
>
> Thanks
> Martin
>
> On 10/02/2016 02:10 PM, Martin Sebor wrote:
>> The attached patch fixes a number of outstanding test failures
>> and ILP32-related bugs in the gimple-ssa-sprintf pattch pointed
>> out in bug 77676 and 77735).  The patch also fixes c_strlen to
>> correctly handle wide strings (previously it accepted them but
>> treated them as nul-terminated byte sequences), and adjusts the
>> handling of "%a" to avoid assuming a specific number of decimal
>> digits (this is likely a defect in C11 that I'm pursuing with
>> WG14).
>>
>> Tested on powerpc64le, i386, and x86_64.
>>
>> There is one outstanding failure in the builtin-sprintf-warn-1.c
>> test on powerpc64le that looks like it might be due to the
>> printf_pointer_format target hook not having been set up entirely
>> correctly.  I'll look into that separately, along with pr77819.
>>
>> Martin
>
Jeff Law Oct. 17, 2016, 7:11 p.m. UTC | #3
On 10/02/2016 02:10 PM, Martin Sebor wrote:
> The attached patch fixes a number of outstanding test failures
> and ILP32-related bugs in the gimple-ssa-sprintf pattch pointed
> out in bug 77676 and 77735).  The patch also fixes c_strlen to
> correctly handle wide strings (previously it accepted them but
> treated them as nul-terminated byte sequences), and adjusts the
> handling of "%a" to avoid assuming a specific number of decimal
> digits (this is likely a defect in C11 that I'm pursuing with
> WG14).
>
> Tested on powerpc64le, i386, and x86_64.
>
> There is one outstanding failure in the builtin-sprintf-warn-1.c
> test on powerpc64le that looks like it might be due to the
> printf_pointer_format target hook not having been set up entirely
> correctly.  I'll look into that separately, along with pr77819.
>
> Martin
>
> gcc-77735.diff
>
>
> PR middle-end/77735 - FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
>
> gcc/ChangeLog:
> 2016-10-02  Martin Sebor  <msebor@redhat.com>
>
> 	PR middle-end/77735
> 	* builtins.c (string_length): New function.
> 	(c_strlen): Use string_length.  Correctly handle wide strings.
> 	* gimple-ssa-sprintf.c (target_max_value, target_size_max): New
> 	functions.
> 	(target_int_max): Call target_max_value.
> 	(format_result::knownrange): New data member.
> 	(fmtresult::fmtresult): Define default constructor.
> 	(format_integer): Use it and set format_result::knownrange.
> 	Handle global constants.
> 	(format_floating_max): Add third argument.
> 	(format_floating): Recompute maximum value for %a for each argument.
> 	(get_string_length): Use fmtresult default ctor.
> 	(format_string): Set format_result::knownrange.
> 	(format_directive): Check format_result::knownrange.
> 	(add_bytes): Same.  Correct caret placement in diagnostics.
> 	(pass_sprintf_length::compute_format_length): Set
> 	format_result::knownrange.
> 	(pass_sprintf_length::handle_gimple_call): Use target_size_max.
>
> gcc/testsuite/ChangeLog:
> 2016-10-02  Martin Sebor  <msebor@redhat.com>
>
> 	PR middle-end/77735
> 	* gcc.dg/tree-ssa/builtin-sprintf-2.c: Add test cases.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Same.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: Same.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Adjust/relax.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: Add test cases.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL for LP64 only.
> 	* gcc.dg/tree-ssa/builtin-sprintf.c: Add test cases.
>
> diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
> index 92f939e..410bbc3 100644
> --- a/gcc/gimple-ssa-sprintf.c
> +++ b/gcc/gimple-ssa-sprintf.c
> @@ -1281,9 +1330,9 @@ format_floating (const conversion_spec &spec, int width, int prec)
>  	res.range.max = width;
>      }
>
> -  /* The argument is only considered bounded when the range of output
> -     bytes is exact.  */
> -  res.bounded = res.range.min == res.range.max;
> +  /* The argument is considered bounded when the output of a directive
> +     is fully specified and the range of output bytes is exact.  */
> +  // res.bounded &= res.range.min == res.range.max;
Did you mean to leave this commented out?

It looks like you're defining a constructor for "struct fmtresult". 
Normally once we define a constructor we turn the underlying object into 
a class.    It appears that the constructor leaves argmin, argmax, 
knownrange, bounded & constant uninitialized?  Am I missing something here?


> +	  /* A plain '%c' directive.  Its ouput is excactly 1.  */
Typo for exactly.


Jeff
Martin Sebor Oct. 17, 2016, 9:59 p.m. UTC | #4
On 10/17/2016 01:11 PM, Jeff Law wrote:
> On 10/02/2016 02:10 PM, Martin Sebor wrote:
>> The attached patch fixes a number of outstanding test failures
>> and ILP32-related bugs in the gimple-ssa-sprintf pattch pointed
>> out in bug 77676 and 77735).  The patch also fixes c_strlen to
>> correctly handle wide strings (previously it accepted them but
>> treated them as nul-terminated byte sequences), and adjusts the
>> handling of "%a" to avoid assuming a specific number of decimal
>> digits (this is likely a defect in C11 that I'm pursuing with
>> WG14).
>>
>> Tested on powerpc64le, i386, and x86_64.
>>
>> There is one outstanding failure in the builtin-sprintf-warn-1.c
>> test on powerpc64le that looks like it might be due to the
>> printf_pointer_format target hook not having been set up entirely
>> correctly.  I'll look into that separately, along with pr77819.
>>
>> Martin
>>
>> gcc-77735.diff
>>
>>
>> PR middle-end/77735 - FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
>>
>> gcc/ChangeLog:
>> 2016-10-02  Martin Sebor  <msebor@redhat.com>
>>
>>     PR middle-end/77735
>>     * builtins.c (string_length): New function.
>>     (c_strlen): Use string_length.  Correctly handle wide strings.
>>     * gimple-ssa-sprintf.c (target_max_value, target_size_max): New
>>     functions.
>>     (target_int_max): Call target_max_value.
>>     (format_result::knownrange): New data member.
>>     (fmtresult::fmtresult): Define default constructor.
>>     (format_integer): Use it and set format_result::knownrange.
>>     Handle global constants.
>>     (format_floating_max): Add third argument.
>>     (format_floating): Recompute maximum value for %a for each argument.
>>     (get_string_length): Use fmtresult default ctor.
>>     (format_string): Set format_result::knownrange.
>>     (format_directive): Check format_result::knownrange.
>>     (add_bytes): Same.  Correct caret placement in diagnostics.
>>     (pass_sprintf_length::compute_format_length): Set
>>     format_result::knownrange.
>>     (pass_sprintf_length::handle_gimple_call): Use target_size_max.
>>
>> gcc/testsuite/ChangeLog:
>> 2016-10-02  Martin Sebor  <msebor@redhat.com>
>>
>>     PR middle-end/77735
>>     * gcc.dg/tree-ssa/builtin-sprintf-2.c: Add test cases.
>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Same.
>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: Same.
>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Adjust/relax.
>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: Add test cases.
>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL for LP64 only.
>>     * gcc.dg/tree-ssa/builtin-sprintf.c: Add test cases.
>>
>> diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
>> index 92f939e..410bbc3 100644
>> --- a/gcc/gimple-ssa-sprintf.c
>> +++ b/gcc/gimple-ssa-sprintf.c
>> @@ -1281,9 +1330,9 @@ format_floating (const conversion_spec &spec,
>> int width, int prec)
>>      res.range.max = width;
>>      }
>>
>> -  /* The argument is only considered bounded when the range of output
>> -     bytes is exact.  */
>> -  res.bounded = res.range.min == res.range.max;
>> +  /* The argument is considered bounded when the output of a directive
>> +     is fully specified and the range of output bytes is exact.  */
>> +  // res.bounded &= res.range.min == res.range.max;
> Did you mean to leave this commented out?

I'm pretty sure I didn't mean to leave the code commented out like
that.  I suspect I commented it out to confirm that it's not needed,
and forgot to remove it after it passed my testing.  Let me remove
it (and thanks for pointing it out!)

> It looks like you're defining a constructor for "struct fmtresult".
> Normally once we define a constructor we turn the underlying object into
> a class.    It appears that the constructor leaves argmin, argmax,
> knownrange, bounded & constant uninitialized?  Am I missing something here?

I added the default ctor to avoid having to explicitly clear the
members.  The member() syntax means to default-initialize them by
setting them to zero or null:

  struct fmtresult
  {
+  fmtresult ()
+  : argmin (), argmax (), knownrange (), bounded (), constant ()
+  {
+    range.min = range.max = HOST_WIDE_INT_MAX;
+  }
+

Or did you have something else in mind?  (I want to leave the members
public so they can conveniently accessed.)

>
>
>> +      /* A plain '%c' directive.  Its ouput is excactly 1.  */
> Typo for exactly.
>

Will fix.

With that and with the comment above removed, would like me to post
an updated patch or is it okay commit?

Martin
Jeff Law Oct. 19, 2016, 10:02 p.m. UTC | #5
On 10/17/2016 03:59 PM, Martin Sebor wrote:
>
>
> On 10/17/2016 01:11 PM, Jeff Law wrote:
>> On 10/02/2016 02:10 PM, Martin Sebor wrote:
>>> The attached patch fixes a number of outstanding test failures
>>> and ILP32-related bugs in the gimple-ssa-sprintf pattch pointed
>>> out in bug 77676 and 77735).  The patch also fixes c_strlen to
>>> correctly handle wide strings (previously it accepted them but
>>> treated them as nul-terminated byte sequences), and adjusts the
>>> handling of "%a" to avoid assuming a specific number of decimal
>>> digits (this is likely a defect in C11 that I'm pursuing with
>>> WG14).
>>>
>>> Tested on powerpc64le, i386, and x86_64.
>>>
>>> There is one outstanding failure in the builtin-sprintf-warn-1.c
>>> test on powerpc64le that looks like it might be due to the
>>> printf_pointer_format target hook not having been set up entirely
>>> correctly.  I'll look into that separately, along with pr77819.
>>>
>>> Martin
>>>
>>> gcc-77735.diff
>>>
>>>
>>> PR middle-end/77735 - FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
>>>
>>> gcc/ChangeLog:
>>> 2016-10-02  Martin Sebor  <msebor@redhat.com>
>>>
>>>     PR middle-end/77735
>>>     * builtins.c (string_length): New function.
>>>     (c_strlen): Use string_length.  Correctly handle wide strings.
>>>     * gimple-ssa-sprintf.c (target_max_value, target_size_max): New
>>>     functions.
>>>     (target_int_max): Call target_max_value.
>>>     (format_result::knownrange): New data member.
>>>     (fmtresult::fmtresult): Define default constructor.
>>>     (format_integer): Use it and set format_result::knownrange.
>>>     Handle global constants.
>>>     (format_floating_max): Add third argument.
>>>     (format_floating): Recompute maximum value for %a for each argument.
>>>     (get_string_length): Use fmtresult default ctor.
>>>     (format_string): Set format_result::knownrange.
>>>     (format_directive): Check format_result::knownrange.
>>>     (add_bytes): Same.  Correct caret placement in diagnostics.
>>>     (pass_sprintf_length::compute_format_length): Set
>>>     format_result::knownrange.
>>>     (pass_sprintf_length::handle_gimple_call): Use target_size_max.
>>>
>>> gcc/testsuite/ChangeLog:
>>> 2016-10-02  Martin Sebor  <msebor@redhat.com>
>>>
>>>     PR middle-end/77735
>>>     * gcc.dg/tree-ssa/builtin-sprintf-2.c: Add test cases.
>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Same.
>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: Same.
>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Adjust/relax.
>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: Add test cases.
>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL for LP64 only.
>>>     * gcc.dg/tree-ssa/builtin-sprintf.c: Add test cases.
>>>
>>> diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
>>> index 92f939e..410bbc3 100644
>>> --- a/gcc/gimple-ssa-sprintf.c
>>> +++ b/gcc/gimple-ssa-sprintf.c
>>> @@ -1281,9 +1330,9 @@ format_floating (const conversion_spec &spec,
>>> int width, int prec)
>>>      res.range.max = width;
>>>      }
>>>
>>> -  /* The argument is only considered bounded when the range of output
>>> -     bytes is exact.  */
>>> -  res.bounded = res.range.min == res.range.max;
>>> +  /* The argument is considered bounded when the output of a directive
>>> +     is fully specified and the range of output bytes is exact.  */
>>> +  // res.bounded &= res.range.min == res.range.max;
>> Did you mean to leave this commented out?
>
> I'm pretty sure I didn't mean to leave the code commented out like
> that.  I suspect I commented it out to confirm that it's not needed,
> and forgot to remove it after it passed my testing.  Let me remove
> it (and thanks for pointing it out!)
>
>> It looks like you're defining a constructor for "struct fmtresult".
>> Normally once we define a constructor we turn the underlying object into
>> a class.    It appears that the constructor leaves argmin, argmax,
>> knownrange, bounded & constant uninitialized?  Am I missing something
>> here?
>
> I added the default ctor to avoid having to explicitly clear the
> members.  The member() syntax means to default-initialize them by
> setting them to zero or null:
>
>  struct fmtresult
>  {
> +  fmtresult ()
> +  : argmin (), argmax (), knownrange (), bounded (), constant ()
> +  {
> +    range.min = range.max = HOST_WIDE_INT_MAX;
> +  }
> +
>
> Or did you have something else in mind?  (I want to leave the members
> public so they can conveniently accessed.)
Ah, thanks for clarifying.

>
>>
>>
>>> +      /* A plain '%c' directive.  Its ouput is excactly 1.  */
>> Typo for exactly.
>>
>
> Will fix.
>
> With that and with the comment above removed, would like me to post
> an updated patch or is it okay commit?
OK to commit after the fix in floating_format (assignment to 
res.bounded) and the commit fix.

jeff
diff mbox

Patch

PR middle-end/77735 - FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c

gcc/ChangeLog:
2016-10-02  Martin Sebor  <msebor@redhat.com>

	PR middle-end/77735
	* builtins.c (string_length): New function.
	(c_strlen): Use string_length.  Correctly handle wide strings.
	* gimple-ssa-sprintf.c (target_max_value, target_size_max): New
	functions.
	(target_int_max): Call target_max_value.
	(format_result::knownrange): New data member.
	(fmtresult::fmtresult): Define default constructor.
	(format_integer): Use it and set format_result::knownrange.
	Handle global constants.
	(format_floating_max): Add third argument.
	(format_floating): Recompute maximum value for %a for each argument.
	(get_string_length): Use fmtresult default ctor.
	(format_string): Set format_result::knownrange.
	(format_directive): Check format_result::knownrange.
	(add_bytes): Same.  Correct caret placement in diagnostics.
	(pass_sprintf_length::compute_format_length): Set
	format_result::knownrange.
	(pass_sprintf_length::handle_gimple_call): Use target_size_max.

gcc/testsuite/ChangeLog:
2016-10-02  Martin Sebor  <msebor@redhat.com>

	PR middle-end/77735
	* gcc.dg/tree-ssa/builtin-sprintf-2.c: Add test cases.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Same.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: Same.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Adjust/relax.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: Add test cases.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL for LP64 only.
	* gcc.dg/tree-ssa/builtin-sprintf.c: Add test cases.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 35cb109..7f6dc54 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -508,9 +508,44 @@  get_pointer_alignment (tree exp)
   return align;
 }
 
-/* Compute the length of a C string.  TREE_STRING_LENGTH is not the right
-   way, because it could contain a zero byte in the middle.
-   TREE_STRING_LENGTH is the size of the character array, not the string.
+/* Return the number of non-zero elements in the sequence
+   [ PTR, PTR + MAXELTS ) where each element's size is ELTSIZE bytes.
+   ELTSIZE must be a power of 2 less than 8.  Used by c_strlen.  */
+
+static unsigned
+string_length (const void *ptr, unsigned eltsize, unsigned maxelts)
+{
+  gcc_checking_assert (eltsize == 1 || eltsize == 2 || eltsize == 4);
+
+  unsigned n;
+
+  if (eltsize == 1)
+    {
+      /* Optimize the common case of plain char.  */
+      for (n = 0; n < maxelts; n++)
+	{
+	  const char *elt = (const char*) ptr + n;
+	  if (!*elt)
+	    break;
+	}
+    }
+  else
+    {
+      for (n = 0; n < maxelts; n++)
+	{
+	  const char *elt = (const char*) ptr + n * eltsize;
+	  if (!memcmp (elt, "\0\0\0\0", eltsize))
+	    break;
+	}
+    }
+  return n;
+}
+
+/* Compute the length of a null-terminated character string or wide
+   character string handling character sizes of 1, 2, and 4 bytes.
+   TREE_STRING_LENGTH is not the right way because it evaluates to
+   the size of the character array in bytes (as opposed to characters)
+   and because it can contain a zero byte in the middle.
 
    ONLY_VALUE should be nonzero if the result is not going to be emitted
    into the instruction stream and zero if it is going to be expanded.
@@ -531,12 +566,6 @@  get_pointer_alignment (tree exp)
 tree
 c_strlen (tree src, int only_value)
 {
-  tree offset_node;
-  HOST_WIDE_INT offset;
-  int max;
-  const char *ptr;
-  location_t loc;
-
   STRIP_NOPS (src);
   if (TREE_CODE (src) == COND_EXPR
       && (only_value || !TREE_SIDE_EFFECTS (TREE_OPERAND (src, 0))))
@@ -553,25 +582,36 @@  c_strlen (tree src, int only_value)
       && (only_value || !TREE_SIDE_EFFECTS (TREE_OPERAND (src, 0))))
     return c_strlen (TREE_OPERAND (src, 1), only_value);
 
-  loc = EXPR_LOC_OR_LOC (src, input_location);
+  location_t loc = EXPR_LOC_OR_LOC (src, input_location);
 
-  src = string_constant (src, &offset_node);
+  /* Offset from the beginning of the string in bytes.  */
+  tree byteoff;
+  src = string_constant (src, &byteoff);
   if (src == 0)
     return NULL_TREE;
 
-  max = TREE_STRING_LENGTH (src) - 1;
-  ptr = TREE_STRING_POINTER (src);
+  /* Determine the size of the string element.  */
+  unsigned eltsize
+    = tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (src))));
+
+  /* Set MAXELTS to sizeof (SRC) / sizeof (*SRC) - 1, the maximum possible
+     length of SRC.  */
+  unsigned maxelts = TREE_STRING_LENGTH (src) / eltsize - 1;
 
-  if (offset_node && TREE_CODE (offset_node) != INTEGER_CST)
+  /* PTR can point to the byte representation of any string type, including
+     char* and wchar_t*.  */
+  const char *ptr = TREE_STRING_POINTER (src);
+
+  if (byteoff && TREE_CODE (byteoff) != INTEGER_CST)
     {
       /* If the string has an internal zero byte (e.g., "foo\0bar"), we can't
 	 compute the offset to the following null if we don't know where to
 	 start searching for it.  */
-      int i;
-
-      for (i = 0; i < max; i++)
-	if (ptr[i] == 0)
+      if (string_length (ptr, eltsize, maxelts) < maxelts)
+	{
+	  /* Return when an embedded null character is found.  */
 	  return NULL_TREE;
+	}
 
       /* We don't know the starting offset, but we do know that the string
 	 has no internal zero bytes.  We can assume that the offset falls
@@ -580,27 +620,31 @@  c_strlen (tree src, int only_value)
 	 and return that.  This would perhaps not be valid if we were dealing
 	 with named arrays in addition to literal string constants.  */
 
-      return size_diffop_loc (loc, size_int (max), offset_node);
+      return size_diffop_loc (loc, size_int (maxelts * eltsize), byteoff);
     }
 
+  /* Offset from the beginning of the string in elements.  */
+  HOST_WIDE_INT eltoff;
+
   /* We have a known offset into the string.  Start searching there for
      a null character if we can represent it as a single HOST_WIDE_INT.  */
-  if (offset_node == 0)
-    offset = 0;
-  else if (! tree_fits_shwi_p (offset_node))
-    offset = -1;
+  if (byteoff == 0)
+    eltoff = 0;
+  else if (! tree_fits_shwi_p (byteoff))
+    eltoff = -1;
   else
-    offset = tree_to_shwi (offset_node);
+    eltoff = tree_to_shwi (byteoff) / eltsize;
 
   /* If the offset is known to be out of bounds, warn, and call strlen at
      runtime.  */
-  if (offset < 0 || offset > max)
+  if (eltoff < 0 || eltoff > maxelts)
     {
      /* Suppress multiple warnings for propagated constant strings.  */
       if (only_value != 2
 	  && !TREE_NO_WARNING (src))
         {
-          warning_at (loc, 0, "offset outside bounds of constant string");
+          warning_at (loc, 0, "offset %qwi outside bounds of constant string",
+		      eltoff);
           TREE_NO_WARNING (src) = 1;
         }
       return NULL_TREE;
@@ -610,9 +654,12 @@  c_strlen (tree src, int only_value)
      constructed with build_string will have nulls appended, we win even
      if we get handed something like (char[4])"abcd".
 
-     Since OFFSET is our starting index into the string, no further
+     Since ELTOFF is our starting index into the string, no further
      calculation is needed.  */
-  return ssize_int (strlen (ptr + offset));
+  unsigned len = string_length (ptr + eltoff * eltsize, eltsize,
+				maxelts - eltoff);
+
+  return ssize_int (len);
 }
 
 /* Return a constant integer corresponding to target reading
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 92f939e..410bbc3 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -79,6 +79,11 @@  along with GCC; see the file COPYING3.  If not see
 #include "substring-locations.h"
 #include "diagnostic.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
+   to be used for optimization but it's good enough as is for warnings.  */
+#define target_mb_len_max   6
+
 namespace {
 
 const pass_data pass_data_sprintf_length = {
@@ -150,17 +155,30 @@  struct format_result
   unsigned HOST_WIDE_INT number_chars_max;
 
   /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX
-     is the output of all directives determined to be bounded to some
-     subrange of their types or possible lengths, false otherwise.
+     can be relied on for value range propagation, false otherwise.
+     This means that BOUNDED must not be set if the number of bytes
+     produced by any directive is unspecified or implementation-
+     defined (unless the implementation's behavior is known and
+     determined via a target hook).
      Note that BOUNDED only implies that the length of a function's
      output is known to be within some range, not that it's constant
-     and a candidate for folding.  */
+     and a candidate for string folding.  BOUNDED is a stronger
+     guarantee than KNOWNRANGE.  */
   bool bounded;
 
+  /* True when the range above is obtained from known values of
+     directive arguments or their bounds and not the result of
+     heuristics that depend on warning levels.  It is used to
+     issue stricter diagnostics in cases where strings of unknown
+     lengths are bounded by the arrays they are determined to
+     refer to.  KNOWNRANGE must not be used to set the range of
+     the return value of a call.  */
+  bool knownrange;
+
   /* True when the output of the formatted call is constant (and
      thus a candidate for string constant folding).  This is rare
      and typically requires that the arguments of all directives
-     are also constant.  Constant implies bounded.  */
+     are also constant.  CONSTANT implies BOUNDED.  */
   bool constant;
 
   /* True if no individual directive resulted in more than 4095 bytes
@@ -216,15 +234,31 @@  target_int_min ()
   return int_min;
 }
 
-/* Return the value of INT_MAX for the target.  */
+/* Return the largest value for TYPE on the target.  */
 
 static unsigned HOST_WIDE_INT
-target_int_max ()
+target_max_value (tree type)
 {
-  const unsigned HOST_WIDE_INT int_max
+  const unsigned HOST_WIDE_INT max_value
     = HOST_WIDE_INT_M1U >> (HOST_BITS_PER_WIDE_INT
-			    - TYPE_PRECISION (integer_type_node) + 1);
-  return int_max;
+			    - TYPE_PRECISION (type) + 1);
+  return max_value;
+}
+
+/* Return the value of INT_MAX for the target.  */
+
+static inline unsigned HOST_WIDE_INT
+target_int_max ()
+{
+  return target_max_value (integer_type_node);
+}
+
+/* Return the value of SIZE_MAX for the target.  */
+
+static inline unsigned HOST_WIDE_INT
+target_size_max ()
+{
+  return target_max_value (size_type_node);
 }
 
 /* Return the constant initial value of DECL if available or DECL
@@ -412,6 +446,12 @@  struct result_range
 
 struct fmtresult
 {
+  fmtresult ()
+  : argmin (), argmax (), knownrange (), bounded (), constant ()
+  {
+    range.min = range.max = HOST_WIDE_INT_MAX;
+  }
+
   /* The range a directive's argument is in.  */
   tree argmin, argmax;
 
@@ -419,11 +459,17 @@  struct fmtresult
      results in on output for an argument in the range above.  */
   result_range range;
 
+  /* True when the range above is obtained from a known value of
+     a directive's argument or its bounds and not the result of
+     heuristics that depend on warning levels.  */
+  bool knownrange;
+
   /* True when the range is the result of an argument determined
      to be bounded to a subrange of its type or value (such as by
      value range propagation or the width of the formt directive),
      false otherwise.  */
   bool bounded;
+
   /* True when the output of a directive is constant.  This is rare
      and typically requires that the argument(s) of the directive
      are also constant (such as determined by constant propagation,
@@ -730,7 +776,7 @@  format_integer (const conversion_spec &, tree);
 static fmtresult
 format_pointer (const conversion_spec &spec, tree arg)
 {
-  fmtresult res = fmtresult ();
+  fmtresult res;
 
   /* Determine the target's integer format corresponding to "%p".  */
   const char *flags;
@@ -869,14 +915,7 @@  format_integer (const conversion_spec &spec, tree arg)
       break;
 
     default:
-      {
-	fmtresult res = fmtresult ();
-	res.range.min = HOST_WIDE_INT_MAX;
-	res.range.max = HOST_WIDE_INT_MAX;
-	res.bounded = false;
-	res.constant = false;
-	return res;
-      }
+	return fmtresult ();
     }
 
   /* The type of the argument to the directive, either deduced from
@@ -897,12 +936,13 @@  format_integer (const conversion_spec &spec, tree arg)
     {
       /* The minimum and maximum number of bytes produced by
 	 the directive.  */
-      fmtresult res = fmtresult ();
+      fmtresult res;
 
       /* When a constant argument has been provided use its value
 	 rather than type to determine the length of the output.  */
       res.bounded = true;
       res.constant = true;
+      res.knownrange = true;
 
       /* Base to format the number in.  */
       int base;
@@ -975,12 +1015,10 @@  format_integer (const conversion_spec &spec, tree arg)
       /* Don't bother with invalid arguments since they likely would
 	 have already been diagnosed, and disable any further checking
 	 of the format string by returning [-1, -1].  */
-      fmtresult res = fmtresult ();
-      res.range.min = res.range.max = HOST_WIDE_INT_M1U;
-      return res;
+      return fmtresult ();
     }
 
-  fmtresult res = fmtresult ();
+  fmtresult res;
 
   /* Using either the range the non-constant argument is in, or its
      type (either "formal" or actual), create a range of values that
@@ -1029,9 +1067,10 @@  format_integer (const conversion_spec &spec, tree arg)
 	      argmax = res.argmax;
 	    }
 
-	  /* The argument is bounded by the range of values determined
-	     by Value Range Propagation.  */
+	  /* The argument is bounded by the known range of values
+	     determined by Value Range Propagation.  */
 	  res.bounded = true;
+	  res.knownrange = true;
 	}
       else if (range_type == VR_ANTI_RANGE)
 	{
@@ -1047,6 +1086,12 @@  format_integer (const conversion_spec &spec, tree arg)
 	  if (is_gimple_assign (def))
 	    {
 	      tree_code code = gimple_assign_rhs_code (def);
+	      if (code == INTEGER_CST)
+		{
+		  arg = gimple_assign_rhs1 (def);
+		  return format_integer (spec, arg);
+		}
+
 	      if (code == NOP_EXPR)
 		argtype = TREE_TYPE (gimple_assign_rhs1 (def));
 	    }
@@ -1111,7 +1156,7 @@  format_integer (const conversion_spec &spec, tree arg)
    SPEC the largest value in the real floating TYPE.  */
 
 static int
-format_floating_max (tree type, char spec)
+format_floating_max (tree type, char spec, int prec = -1)
 {
   machine_mode mode = TYPE_MODE (type);
 
@@ -1136,9 +1181,21 @@  format_floating_max (tree type, char spec)
   mpfr_init2 (x, rfmt->p);
   mpfr_from_real (x, &rv, GMP_RNDN);
 
-  const char fmt[] = { '%', 'R', spec, '\0' };
-  int n = mpfr_snprintf (NULL, 0, fmt, x);
-  return n;
+  int n;
+
+  if (-1 < prec)
+    {
+      const char fmt[] = { '%', '.', '*', 'R', spec, '\0' };
+      n = mpfr_snprintf (NULL, 0, fmt, prec, x);
+    }
+  else
+    {
+      const char fmt[] = { '%', 'R', spec, '\0' };
+      n = mpfr_snprintf (NULL, 0, fmt, x);
+    }
+
+  /* Return a value one greater to account for the leading minus sign.  */
+  return n + 1;
 }
 
 /* Return a range representing the minimum and maximum number of bytes
@@ -1170,19 +1227,11 @@  format_floating (const conversion_spec &spec, int width, int prec)
       break;
 
     default:
-      {
-	fmtresult res = fmtresult ();
-	res.range.min = HOST_WIDE_INT_MAX;
-	res.range.max = HOST_WIDE_INT_MAX;
-	res.bounded = false;
-	res.constant = false;
-	return res;
-      }
+      return fmtresult ();
     }
 
   /* The minimum and maximum number of bytes produced by the directive.  */
-  fmtresult res = fmtresult ();
-  res.constant = false;
+  fmtresult res;
 
   /* Log10 of of the maximum number of exponent digits for the type.  */
   int logexpdigs = 2;
@@ -1206,13 +1255,11 @@  format_floating (const conversion_spec &spec, int width, int prec)
       {
 	/* The minimum output is "0x.p+0".  */
 	res.range.min = 6 + (prec > 0 ? prec : 0);
+	res.range.max = format_floating_max (type, 'a', prec);
 
-	/* Compute the maximum just once.  */
-	static const int a_max[] = {
-	  format_floating_max (double_type_node, 'a'),
-	  format_floating_max (long_double_type_node, 'a')
-	};
-	res.range.max = a_max [ldbl];
+	/* The output of "%a" is fully specified only when precision
+	   is explicitly specified.  */
+	res.bounded = -1 < prec;
 	break;
       }
 
@@ -1229,6 +1276,9 @@  format_floating (const conversion_spec &spec, int width, int prec)
 	   included), plus the difference between the minimum exponent
 	   of 2 and the maximum exponent for the type.  */
 	res.range.max = res.range.min + !sign + logexpdigs - 2;
+
+	/* "%e" is fully specified and the range of bytes is bounded.  */
+	res.bounded = true;
 	break;
       }
 
@@ -1245,6 +1295,9 @@  format_floating (const conversion_spec &spec, int width, int prec)
 	  format_floating_max (long_double_type_node, 'f')
 	};
 	res.range.max = f_max [ldbl];
+
+	/* "%f" is fully specified and the range of bytes is bounded.  */
+	res.bounded = true;
 	break;
       }
     case 'G':
@@ -1259,18 +1312,14 @@  format_floating (const conversion_spec &spec, int width, int prec)
 	  format_floating_max (long_double_type_node, 'g')
 	};
 	res.range.max = g_max [ldbl];
+
+	/* "%g" is fully specified and the range of bytes is bounded.  */
+	res.bounded = true;
 	break;
       }
 
     default:
-      {
-	fmtresult res = fmtresult ();
-	res.range.min = HOST_WIDE_INT_MAX;
-	res.range.max = HOST_WIDE_INT_MAX;
-	res.bounded = false;
-	res.constant = false;
-	return res;
-      }
+      return fmtresult ();
     }
 
   if (width > 0)
@@ -1281,9 +1330,9 @@  format_floating (const conversion_spec &spec, int width, int prec)
 	res.range.max = width;
     }
 
-  /* The argument is only considered bounded when the range of output
-     bytes is exact.  */
-  res.bounded = res.range.min == res.range.max;
+  /* The argument is considered bounded when the output of a directive
+     is fully specified and the range of output bytes is exact.  */
+  // res.bounded &= res.range.min == res.range.max;
   return res;
 }
 
@@ -1298,7 +1347,7 @@  format_floating (const conversion_spec &spec, tree arg)
   int prec = -1;
 
   /* The minimum and maximum number of bytes produced by the directive.  */
-  fmtresult res = fmtresult ();
+  fmtresult res;
   res.constant = arg && TREE_CODE (arg) == REAL_CST;
 
   if (spec.have_width)
@@ -1390,7 +1439,16 @@  format_floating (const conversion_spec &spec, tree arg)
 	  *minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval);
 	}
 
-      res.bounded = res.range.min < target_int_max ();
+      /* The output of all directives except "%a" is fully specified
+	 and so the result is bounded unless it exceeds INT_MAX.
+	 For "%a" the output is fully specified only when precision
+	 is explicitly specified.  */
+      res.bounded = ((TOUPPER (spec.specifier) != 'A'
+		      || (0 <= prec && (unsigned) prec < target_int_max ()))
+		     && res.range.min < target_int_max ());
+
+      /* The range of output is known even if the result isn't bounded.  */
+      res.knownrange = true;
       return res;
     }
 
@@ -1405,14 +1463,7 @@  static fmtresult
 get_string_length (tree str)
 {
   if (!str)
-    {
-      fmtresult res;
-      res.range.min = HOST_WIDE_INT_MAX;
-      res.range.max = HOST_WIDE_INT_MAX;
-      res.bounded = false;
-      res.constant = false;
-      return res;
-    }
+    return fmtresult ();
 
   if (tree slen = c_strlen (str, 1))
     {
@@ -1421,6 +1472,7 @@  get_string_length (tree str)
       res.range.min = res.range.max = tree_to_shwi (slen);
       res.bounded = true;
       res.constant = true;
+      res.knownrange = true;
       return res;
     }
 
@@ -1434,7 +1486,7 @@  get_string_length (tree str)
 
   if (lenrange [0] || lenrange [1])
     {
-      fmtresult res = fmtresult ();
+      fmtresult res;
 
       res.range.min = (tree_fits_uhwi_p (lenrange[0])
 		       ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length);
@@ -1445,11 +1497,13 @@  get_string_length (tree str)
 	 by STR are known to be bounded (though not necessarily by their
 	 actual length but perhaps by their maximum possible length).  */
       res.bounded = res.range.max < target_int_max ();
+      res.knownrange = res.bounded;
 
       /* Set RES.CONSTANT to false even though that may be overly
 	 conservative in rare cases like: 'x ? a : b' where a and
 	 b have the same lengths and consist of the same characters.  */
       res.constant = false;
+
       return res;
     }
 
@@ -1479,7 +1533,7 @@  format_string (const conversion_spec &spec, tree arg)
     prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
 	    ? tree_to_shwi (spec.star_precision) : -1);
 
-  fmtresult res = fmtresult ();
+  fmtresult res;
 
   /* The maximum number of bytes for an unknown wide character argument
      to a "%lc" directive adjusted for precision but not field width.  */
@@ -1515,13 +1569,16 @@  format_string (const conversion_spec &spec, tree arg)
 	     locale, which is unfortunately, unknown.  */
 	  res.range.min = 1 == warn_format_length ? !nul : nul < 1;
 	  res.range.max = max_bytes_for_unknown_wc;
-	  res.bounded = true;
+	  /* The range above is good enough to issue warnings but not
+	     for value range propagation, so clear BOUNDED.  */
+	  res.bounded = false;
 	}
       else
 	{
-	  /* A plain '%c' directive.  */
+	  /* A plain '%c' directive.  Its ouput is excactly 1.  */
 	  res.range.min = res.range.max = 1;
 	  res.bounded = true;
+	  res.knownrange = true;
 	  res.constant = arg && TREE_CODE (arg) == INTEGER_CST;
 	}
     }
@@ -1533,24 +1590,35 @@  format_string (const conversion_spec &spec, tree arg)
 	{
 	  gcc_checking_assert (slen.range.min == slen.range.max);
 
-	  res.bounded = true;
-
 	  /* A '%s' directive with a string argument with constant length.  */
 	  res.range = slen.range;
 
+	  /* The output of "%s" and "%ls" directives with a constant
+	     string is in a known range.  For "%s" it is the length
+	     of the string.  For "%ls" it is in the range [length,
+	     length * MB_LEN_MAX].  (The final range can be further
+	     constrained by width and precision but it's always known.)  */
+	  res.knownrange = true;
+
 	  if (spec.modifier == FMT_LEN_l)
 	    {
-	      if (warn_format_length > 2)
+	      bounded = false;
+
+	      if (warn_format_length > 1)
 		{
-		  res.range.min *= 6;
+		  /* Leave the minimum number of bytes the wide string
+		     converts to equal to its length and set the maximum
+		     to the worst case length which is the string length
+		     multiplied by MB_LEN_MAX.  */
 
 		  /* It's possible to be smarter about computing the maximum
 		     by scanning the wide string for any 8-bit characters and
 		     if it contains none, using its length for the maximum.
 		     Even though this would be simple to do it's unlikely to
 		     be worth it when dealing with wide characters.  */
-		  res.range.max *= 6;
+		  res.range.max *= target_mb_len_max;
 		}
+
 	      /* For a wide character string, use precision as the maximum
 		 even if precision is greater than the string length since
 		 the number of bytes the string converts to may be greater
@@ -1559,7 +1627,12 @@  format_string (const conversion_spec &spec, tree arg)
 		res.range.max = prec;
 	    }
 	  else
-	    res.constant = true;
+	    {
+	      /* The output od a "%s" directive with a constant argument
+		 is bounded, constant, and obviously in a known range.  */
+	      res.bounded = true;
+	      res.constant = true;
+	    }
 
 	  if (0 <= prec && (unsigned)prec < res.range.min)
 	    {
@@ -1577,9 +1650,11 @@  format_string (const conversion_spec &spec, tree arg)
 
 	  if (0 <= prec)
 	    {
-	      if ((unsigned)prec < slen.range.min
-		  || slen.range.min >= target_int_max ())
+	      if (slen.range.min >= target_int_max ())
+		slen.range.min = max_bytes_for_unknown_str;
+	      else if ((unsigned)prec < slen.range.min)
 		slen.range.min = prec;
+
 	      if ((unsigned)prec < slen.range.max
 		  || slen.range.max >= target_int_max ())
 		slen.range.max = prec;
@@ -1597,6 +1672,7 @@  format_string (const conversion_spec &spec, tree arg)
 	     specified to limit the number of bytes or when the number
 	     of bytes is known or contrained to some range.  */
 	  res.bounded = 0 <= prec || slen.bounded;
+	  res.knownrange = slen.knownrange;
 	  res.constant = false;
 	}
     }
@@ -1613,6 +1689,11 @@  format_string (const conversion_spec &spec, tree arg)
       && bounded)
     res.bounded = true;
 
+  /* When precision is specified the range of characters on output
+     is known to be bounded by it.  */
+  if (-1 < prec)
+    res.knownrange = true;
+
   return res;
 }
 
@@ -1655,29 +1736,46 @@  format_directive (const pass_sprintf_length::call_info &info,
   /* Compute the (approximate) length of the formatted output.  */
   fmtresult fmtres = spec.fmtfunc (spec, arg);
 
-  /* The overall result is bounded only if the output of every
-     directive is exact or bounded.  */
-  res->bounded = res->bounded && fmtres.bounded;
-  res->constant = res->constant && fmtres.constant;
+  /* The overall result is bounded and constant only if the output
+     of every directive is bounded and constant, respectively.  */
+  res->bounded &= fmtres.bounded;
+  res->constant &= fmtres.constant;
 
-  if (fmtres.range.max >= HOST_WIDE_INT_MAX)
-    {
-      /* Disable exact and maximum length checking after a failure
-	 to determine the maximum number of characters (for example
-	 for wide characters or wide character strings) but continue
-	 tracking the minimum number of characters.  */
-      res->number_chars_max = HOST_WIDE_INT_M1U;
-      res->number_chars = HOST_WIDE_INT_M1U;
-    }
+  /* Record whether the output of all directives is known to be
+     bounded by some maximum, implying that their arguments are
+     either known exactly or determined to be in a known range
+     or, for strings, limited by the upper bounds of the arrays
+     they refer to.  */
+  res->knownrange &= fmtres.knownrange;
 
-  if (fmtres.range.min >= HOST_WIDE_INT_MAX)
+  if (!fmtres.knownrange)
     {
-      /* Disable exact length checking after a failure to determine
-	 even the minimum number of characters (it shouldn't happen
-	 except in an error) but keep tracking the minimum and maximum
-	 number of characters.  */
-      res->number_chars = HOST_WIDE_INT_M1U;
-      return;
+      /* Only when the range is known, check it against the host value
+	 of INT_MAX.  Otherwise the range doesn't correspond to known
+	 values of the argument.  */
+      if (fmtres.range.max >= target_int_max ())
+	{
+	  /* Normalize the MAX counter to avoid having to deal with it
+	     later.  The counter can be less than HOST_WIDE_INT_M1U
+	     when compiling for an ILP32 target on an LP64 host.  */
+	  fmtres.range.max = HOST_WIDE_INT_M1U;
+	  /* Disable exact and maximum length checking after a failure
+	     to determine the maximum number of characters (for example
+	     for wide characters or wide character strings) but continue
+	     tracking the minimum number of characters.  */
+	  res->number_chars_max = HOST_WIDE_INT_M1U;
+	  res->number_chars = HOST_WIDE_INT_M1U;
+	}
+
+      if (fmtres.range.min >= target_int_max ())
+	{
+	  /* Disable exact length checking after a failure to determine
+	     even the minimum number of characters (it shouldn't happen
+	     except in an error) but keep tracking the minimum and maximum
+	     number of characters.  */
+	  res->number_chars = HOST_WIDE_INT_M1U;
+	  return;
+	}
     }
 
   /* Compute the number of available bytes in the destination.  There
@@ -1725,11 +1823,15 @@  format_directive (const pass_sprintf_length::call_info &info,
 		}
 	    }
 	  else if (navail < fmtres.range.max
-		   && (fmtres.bounded || 1 < warn_format_length))
+		   && (((spec.specifier == 's'
+			 && fmtres.range.max < HOST_WIDE_INT_MAX)
+			/* && (spec.precision || spec.star_precision) */)
+		       || 1 < warn_format_length))
 	    {
 	      /* The maximum directive output is longer than there is
-		 room in the destination and the output is either bounded
-		 or the warning level is greater than 1.  */
+		 room in the destination and the output length is either
+		 explicitly constrained by the precision (for strings)
+ 		 or the warning level is greater than 1.  */
 	      if (fmtres.range.max >= HOST_WIDE_INT_MAX)
 		{
 		  const char* fmtstr
@@ -1910,10 +2012,13 @@  add_bytes (const pass_sprintf_length::call_info &info,
 
   /* If issuing a diagnostic (only when one hasn't already been issued),
      distinguish between a possible overflow ("may write") and a certain
-     overflow somewhere "past the end."  (Ditto for truncation.)  */
+     overflow somewhere "past the end."  (Ditto for truncation.)
+     KNOWNRANGE is used to warn even at level 1 about possibly writing
+     past the end or truncation due to strings of unknown lengths that
+     are bounded by the arrays they are known to refer to.  */
   if (!res->warned
       && (avail_range.max < nbytes
-	  || ((res->bounded || 1 < warn_format_length)
+	  || ((res->knownrange || 1 < warn_format_length)
 	      && avail_range.min < nbytes)))
     {
       /* Set NAVAIL to the number of available bytes used to decide
@@ -1921,7 +2026,7 @@  add_bytes (const pass_sprintf_length::call_info &info,
 	 warning will depend on AVAIL_RANGE.  */
       unsigned HOST_WIDE_INT navail = avail_range.max;
       if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX
-	  && (res->bounded || 1 < warn_format_length))
+	  && (res->knownrange || 1 < warn_format_length))
 	navail = avail_range.min;
 
       /* Compute the offset of the first format character that is beyond
@@ -1932,8 +2037,12 @@  add_bytes (const pass_sprintf_length::call_info &info,
 
       size_t len = strlen (info.fmtstr + off);
 
+      /* Create a location that underscores the substring of the format
+	 string that is or may be written past the end (or is or may be
+	 truncated), pointing the caret at the first character of the
+	 substring. */
       substring_loc loc
-	(info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0,
+	(info.fmtloc, TREE_TYPE (info.format), off, len ? off : 0,
 	 off + len - !!len);
 
       /* Is the output of the last directive the result of the argument
@@ -1944,7 +2053,7 @@  add_bytes (const pass_sprintf_length::call_info &info,
 	= (res->number_chars_min < res->number_chars_max
 	   && res->number_chars_min < info.objsize);
 
-      if (!end && (nbytes - navail) == 1)
+      if (!end && ((nbytes - navail) == 1 || boundrange))
 	{
 	  /* There is room for the rest of the format string but none
 	     for the terminating nul.  */
@@ -2082,10 +2191,11 @@  pass_sprintf_length::compute_format_length (const call_info &info,
   /* Reset exact, minimum, and maximum character counters.  */
   res->number_chars = res->number_chars_min = res->number_chars_max = 0;
 
-  /* No directive has been seen yet so the output is bounded and constant
-     (with no conversion producing more than 4K bytes) until determined
-     otherwise.  */
+  /* No directive has been seen yet so the length of output is bounded
+     by the known range [0, 0] and constant (with no conversion producing
+     more than 4K bytes) until determined otherwise.  */
   res->bounded = true;
+  res->knownrange = true;
   res->constant = true;
   res->under4k = true;
   res->floating = false;
@@ -2591,10 +2701,10 @@  pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator gsi)
       if (TREE_CODE (size) == INTEGER_CST)
 	{
 	  dstsize = tree_to_uhwi (size);
-	  /* No object can be larger than HOST_WIDE_INT_MAX bytes
-	     (half the address space).  This imposes a limit that's
-	     one byte less than that.  */
-	  if (dstsize >= HOST_WIDE_INT_MAX)
+	  /* No object can be larger than SIZE_MAX bytes (half the address
+	     space) on the target.  This imposes a limit that's one byte
+	     less than that.  */
+	  if (dstsize >= target_size_max () / 2)
 	    warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
 			"specified destination size %wu too large",
 			dstsize);
@@ -2640,7 +2750,7 @@  pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator gsi)
       info.objsize = dstsize < objsize ? dstsize : objsize;
 
       if (info.bounded
-	  && dstsize != HOST_WIDE_INT_M1U && objsize < dstsize)
+	  && dstsize < target_size_max () / 2 && objsize < dstsize)
 	{
 	  warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
 		      "specified size %wu exceeds the size %wu "
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
index d6a0e6b..1996665 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
@@ -1,5 +1,5 @@ 
 /* Test to verify that the return value of calls to __builtin_sprintf
-   is not folded if the call has undefined behavior even if it would
+   is not folded if the call isn't fully specified, even if it would
    otherwise produce a known number of bytes on output, and that if
    the return value is in a known range the range is not made
    available to subsequent passes and doesn't affect branching and
@@ -22,7 +22,8 @@  char buf8k [8192];
 #define CAT(a, b)      concat (a, b)
 
 #define EQL(expect, size, fmt, ...)					\
-  void CAT (test_on_line_, __LINE__)(void)				\
+  void __attribute__ ((noinline, noclone))				\
+  CAT (test_on_line_, __LINE__)(void)					\
   {									\
     if (!LINE || LINE == __LINE__)					\
       {									\
@@ -37,7 +38,8 @@  char buf8k [8192];
    to the formatted function is not treated as a constant or made available
    to subsequent optimization passes.  */
 #define RNG(min, max, size, fmt, ...)					\
-  void CAT (test_on_line_, __LINE__)(void)				\
+  void __attribute__ ((noinline, noclone))				\
+  CAT (test_on_line_, __LINE__)(void)					\
   {									\
     if (!LINE || LINE == __LINE__)					\
       {									\
@@ -52,6 +54,9 @@  extern int i;
 extern long li;
 extern char *str;
 
+extern double d;
+extern long double ld;
+
 /* Verify that overflowing the destination object disables the return
    value optimization.  */
 EQL (0, 0, "%c",  ' ');
@@ -78,7 +83,15 @@  enum { imax2 = (INT_MAX / 2) * 2 };
 EQL (imax2, -1, "%*c%*c", INT_MAX / 2, 'x', INT_MAX / 2, 'y');
 
 /* Verify that range information for calls that overflow the destination
-   isn't available.  */
+   isn't available.
+
+     +-- lower bound of the tested range
+     |   +-- upper bound of the tested range
+     |   |   +-- size of destination buffer
+     |   |   |  +-- format string
+     |   |   |  |       +-- argument(s)
+     |   |   |  |       |
+     V   V   V  V       V  */
 RNG (0,  0,  0, "%hhi", i)
 RNG (0,  0,  1, "%hhi", i)
 RNG (0,  1,  1, "%hhi", i)
@@ -190,11 +203,36 @@  RNG (0, 10, 10, "%i", i)
 
 #endif
 
+/* Verify that the output of a "%a" directive with no precision is not
+   considered constant or within a known range (the number of digits
+   after the decimal point is unspecified in this case).  The hardcoded
+   ranges correspond to Glibc values.  */
+RNG (6,  6,  7, "%a",       0.0)    /* Glibc output: "0x0p+0"  */
+RNG (6,  6,  7, "%a",       d)
+RNG (6,  6,  7, "%.4096a",  d)
+
+RNG (6,  6,  7, "%La",      0.0L)   /* Glibc output: "0x0p+0"  */
+RNG (6,  6,  7, "%La",      ld)
+RNG (6,  6,  7, "%.4096La", ld)
+
+/* Verify that the result of formatting an unknown string isn't optimized
+   into a non-negative range.  The string could be longer that 4,095 bytes,
+   resulting in the formatting function having undefined behavior (and
+   returning a negative value as Glibc can for some directives).  */
+RNG (0,  INT_MAX, -1, "%-s", str);
+
 /* Verify the result of a conditional expression involving a string
    literal and an unknown string isn't optimized.  */
 RNG (0,  1,   4, "%-s", i ? str : "123");
 RNG (0,  1,   4, "%-s", i ? "123" : str);
 
+/* Verfy that the output involving wide strings is not optimized
+   (the output is actually bounded by a function of MB_LEN_MAX
+   which should be at least 6 to accommodate UTF-8 but this isn't
+   implemented yet).  */
+RNG (0,  5,   7, "%ls",   L"1");
+RNG (0,  6,   8, "%s%ls", "1", L"2");
+
 /* Verify that no call to abort has been eliminated and that each call
    is at the beginning of a basic block (and thus the result of a branch).
    This latter test tries to verify that the test preceding the call to
@@ -214,5 +252,5 @@  RNG (0,  1,   4, "%-s", i ? "123" : str);
 
 */
 
-/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 105 "optimized" { target { ilp32 || lp64 } } } } */
-/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 74 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } } */
+/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 114 "optimized" { target { ilp32 || lp64 } } } } */
+/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 83 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
index b9acf66..3aa4486 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
@@ -37,6 +37,18 @@  typedef __WINT_TYPE__ wint_t;
 
 typedef unsigned char UChar;
 
+/* Constants used to verify the pass can determine their values even
+   without optimization.  */
+const int cst0   =  0;
+const int cst1   =  1;
+const int cst10  = 10;
+
+/* Initialized global variables used to verify that the pass doesn't
+   use their initial values (they could be modified by calls to other
+   functions).  */
+int var0  =  0;
+int var10 = 10;
+
 const char s0[] = "";
 const char s1[] = "1";
 const char s2[] = "12";
@@ -372,6 +384,13 @@  void test_sprintf_chk_s_const (void)
   T (3, "%.0ls",    L"1");
   T (3, "%.1ls",    L"1");
   T (3, "%.2ls",    L"1");
+  T (3, "%ls",      L"12");
+
+  T (3, "%ls",      L"123");    /* { dg-warning "nul past the end" } */
+  T (3, "%.0ls",    L"123");
+  T (3, "%.1ls",    L"123");
+  T (3, "%.2ls",    L"123");
+  T (3, "%.3ls",    L"123");    /* { dg-warning "nul past the end" } */
 }
 
 /* Exercise the "%hhd", "%hhi", "%hho", "%hhu", and "%hhx" directives
@@ -382,7 +401,9 @@  void test_sprintf_chk_hh_const (void)
   T (-1, "%hhd",        0);
 
   T (1, "%hhd",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhd",      cst0);     /* { dg-warning "nul past the end" } */
   T (1, "%hhd",         1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhd",      cst1);     /* { dg-warning "nul past the end" } */
   T (1, "%hhd",        -1);     /* { dg-warning "into a region" } */
   T (1, "%+hhd",        0);     /* { dg-warning "into a region" } */
   T (1, "%+hhd",        1);     /* { dg-warning "into a region" } */
@@ -402,6 +423,7 @@  void test_sprintf_chk_hh_const (void)
   T (2, "%+hhi",        9);     /* { dg-warning "nul past the end" } */
   T (2, "%-hhi",        9);
   T (2, "%hhi",        10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hhi",     cst10);     /* { dg-warning "nul past the end" } */
   T (2, "%hhi",        -1);     /* { dg-warning "nul past the end" } */
   T (2, "% hhi",       -1);     /* { dg-warning "nul past the end" } */
   T (2, "%+hhi",       -1);     /* { dg-warning "nul past the end" } */
@@ -863,6 +885,35 @@  void test_sprintf_chk_z_const (void)
   T ( 2, "%zu",        (size_t)10); /* { dg-warning "nul past the end" } */
 }
 
+void test_sprintf_chk_a_const (void)
+{
+  T (-1, "%a",  0.0);
+  T (-1, "%la", 0.0);
+
+  /* The least number of bytes on output is 6 for "0x0p+0".  When precision
+     is missing the number of digits after the decimal point isn't fully
+     specified by C (it seems like a defect).  */
+  T (0, "%a",   0.0);          /* { dg-warning "into a region" } */
+  T (0, "%la",  0.0);          /* { dg-warning "into a region" } */
+  T (1, "%a",   0.0);          /* { dg-warning "into a region" } */
+  T (2, "%a",   0.0);          /* { dg-warning "into a region" } */
+  T (3, "%a",   0.0);          /* { dg-warning "into a region" } */
+  T (4, "%a",   0.0);          /* { dg-warning "into a region" } */
+  T (5, "%a",   0.0);          /* { dg-warning "into a region" } */
+  T (6, "%a",   0.0);          /* { dg-warning "writing a terminating nul" } */
+  T (7, "%a",   0.0);
+
+  T (0, "%.0a",   0.0);          /* { dg-warning "into a region" } */
+  T (0, "%.0la",  0.0);          /* { dg-warning "into a region" } */
+  T (1, "%.0a",   0.0);          /* { dg-warning "into a region" } */
+  T (2, "%.0a",   0.0);          /* { dg-warning "into a region" } */
+  T (3, "%.0a",   0.0);          /* { dg-warning "into a region" } */
+  T (4, "%.0a",   0.0);          /* { dg-warning "into a region" } */
+  T (5, "%.0a",   0.0);          /* { dg-warning "into a region" } */
+  T (6, "%.0a",   0.0);          /* { dg-warning "writing a terminating nul" } */
+  T (7, "%.0a",   0.0);
+}
+
 void test_sprintf_chk_e_const (void)
 {
   T (-1, "%E",   0.0);
@@ -893,20 +944,23 @@  void test_sprintf_chk_e_const (void)
   T ( 6, "%.0e", 1.0);
 
   /* The actual output of the following directives depends on the rounding
-     mode.  Verify that the warning correctly reflects that.  */
-  T (12, "%e",  9.999999e+99);  /* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%e",  9.9999994e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%e",  9.9999995e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%e",  9.9999996e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%e",  9.9999997e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%e",  9.9999998e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
-
-  T (12, "%Le", 9.9999994e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%Le", 9.9999995e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%Le", 9.9999996e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%Le", 9.9999997e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%Le", 9.9999998e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
-  T (12, "%Le", 9.9999999e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
+     mode.  Verify that the warning correctly reflects that.  At level 1,
+     since the minimum number of bytes output by the directive fits the
+     space the directive itself isn't diagnosed but the terminating nul
+     is.  The directive is diagnosed at level 2.  */
+  T (12, "%e",  9.999999e+99);  /* { dg-warning "terminating nul" } */
+  T (12, "%e",  9.9999994e+99); /* { dg-warning "terminating nul" } */
+  T (12, "%e",  9.9999995e+99); /* { dg-warning "terminating nul" } */
+  T (12, "%e",  9.9999996e+99); /* { dg-warning "terminating nul" } */
+  T (12, "%e",  9.9999997e+99); /* { dg-warning "terminating nul" } */
+  T (12, "%e",  9.9999998e+99); /* { dg-warning "terminating nul" } */
+
+  T (12, "%Le", 9.9999994e+99L);/* { dg-warning "terminating nul" } */
+  T (12, "%Le", 9.9999995e+99L);/* { dg-warning "terminating nul" } */
+  T (12, "%Le", 9.9999996e+99L);/* { dg-warning "terminating nul" } */
+  T (12, "%Le", 9.9999997e+99L);/* { dg-warning "terminating nul" } */
+  T (12, "%Le", 9.9999998e+99L);/* { dg-warning "terminating nul" } */
+  T (12, "%Le", 9.9999999e+99L);/* { dg-warning "terminating nul" } */
 }
 
 /* At -Wformat-length level 1 unknown numbers are assumed to have
@@ -936,12 +990,14 @@  void test_sprintf_chk_hh_nonconst (int a)
 {
   T (-1, "%hhd",        a);
 
-  T (0, "%hhd",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhd",         a);     /* { dg-warning ".%hhd. directive writing between 1 and . bytes into a region of size 0" } */
+  T (0, "%hhi",      var0);     /* { dg-warning "into a region" } */
   T (0, "%hhi",         a);     /* { dg-warning "into a region" } */
   T (0, "%hhu",         a);     /* { dg-warning "into a region" } */
   T (0, "%hhx",         a);     /* { dg-warning "into a region" } */
 
   T (1, "%hhd",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhd",      var0);     /* { dg-warning "nul past the end" } */
   T (1, "%hhi",         a);     /* { dg-warning "nul past the end" } */
   T (1, "%hhu",         a);     /* { dg-warning "nul past the end" } */
   T (1, "%hhx",         a);     /* { dg-warning "nul past the end" } */
@@ -954,19 +1010,23 @@  void test_sprintf_chk_hh_nonconst (int a)
   T (1, "%-hhi",        a);     /* { dg-warning "nul past the end" } */
 
   T (2, "%hhd",         a);
+  T (2, "%hhd",      var0);
+  T (2, "%hhd",     var10);
   T (2, "%hhi",         a);
   T (2, "%hho",         a);
   T (2, "%hhu",         a);
   T (2, "%hhx",         a);
 
   T (2, "% hhd",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hhd",     var0);     /* { dg-warning "nul past the end" } */
+  T (2, "% hhd",    var10);     /* { dg-warning "nul past the end" } */
   T (2, "% hhi",        a);     /* { dg-warning "nul past the end" } */
   T (2, "% hho",        a);     /* { dg-warning ". . flag used with .%o." } */
   T (2, "% hhu",        a);     /* { dg-warning ". . flag used with .%u." } */
   T (2, "% hhx",        a);     /* { dg-warning ". . flag used with .%x." } */
 
-  T (2, "#%hho",        a);     /* { dg-warning "nul past the end" } */
-  T (2, "#%hhx",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "%#hho",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "%#hhx",        a);     /* { dg-warning ".%#hhx. directive writing between 3 and . bytes into a region of size 2" } */
 
   T (3, "%2hhd",        a);
   T (3, "%2hhi",        a);
@@ -1086,8 +1146,7 @@  void test_sprintf_chk_e_nonconst (double d)
   T ( 1, "%e",          d);           /* { dg-warning "into a region" } */
   T ( 2, "%e",          d);           /* { dg-warning "into a region" } */
   T ( 3, "%e",          d);           /* { dg-warning "into a region" } */
-  T (12, "%e",          d);           /* { dg-warning "past the end" } */
-  T (12, "%e",          d);           /* { dg-warning "past the end" } */
+  T (12, "%e",          d);           /* { dg-warning "nul past the end" } */
   T (13, "%E",          d);           /* 1.000000E+00 */
   T (13, "%e",          d);
   T (14, "%E",          d);
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
index 675ecac..e19768e 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
@@ -47,10 +47,13 @@  void test_s_const (void)
   T (1, "%*ls",  0, L"\0");
   T (1, "%*ls",  1, L"");       /* { dg-warning "nul past the end" } */
 
-  T (1, "%ls",      L"1");      /* { dg-warning "nul past the end" } */
+  T (1, "%ls",      L"1");      /* { dg-warning "directive writing between 1 and 6 bytes into a region of size 1" } */
   T (1, "%.0ls",    L"1");
   T (2, "%.0ls",    L"1");
   T (2, "%.1ls",    L"1");
+  T (2, "%.2ls",    L"1");      /* { dg-warning "nul past the end" } */
+  T (2, "%.3ls",    L"1");      /* { dg-warning "directive writing between 1 and 3 bytes into a region of size 2" } */
+  T (2, "%.2ls",    L"12");     /* { dg-warning "nul past the end" } */
 
   /* The "%.2ls" directive below will write at a minimum 1 byte (because
      L"1" is known and can be assumed to convert to at least one multibyte
@@ -64,6 +67,12 @@  void test_s_const (void)
   T (2, "%.0ls",    L"1");
   T (2, "%.1ls",    L"1");
   T (3, "%.2ls",    L"1");
+  T (3, "%.2ls",    L"12");
+  T (3, "%.3ls",    L"12");     /* { dg-warning "nul past the end" } */
+  T (4, "%.3ls",    L"123");
+  T (4, "%.4ls",    L"123");    /* { dg-warning "nul past the end" } */
+  T (4, "%.5ls",    L"123");    /* { dg-warning "directive writing between 3 and 5 bytes into a region of size 4" } */
+  T (4, "%.6ls",    L"123");    /* { dg-warning "directive writing between 3 and 6 bytes into a region of size 4" } */
 }
 
 
@@ -86,7 +95,9 @@  void test_s_nonconst (const char *s, const wchar_t *ws, struct Arrays *a)
   T (1, "%.0s", s);
   T (1, "%.1s", s);             /* { dg-warning "writing a terminating nul" } */
 
-  T (1, "%ls",  ws);            /* { dg-warning "writing a terminating nul" } */
+  T (1, "%.0ls",  ws);
+  T (1, "%.1ls",  ws);          /* { dg-warning "writing a terminating nul" } */
+  T (1, "%ls",    ws);          /* { dg-warning "writing a terminating nul" } */
 
   /* Verify that the size of the array is used in lieu of its length.
      The minus sign disables GCC's sprintf to strcpy transformation.  */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
index 625d055..8d97fa8 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
@@ -175,9 +175,9 @@  void test_sprintf_chk_range_schar (signed char *a)
   T ( 3, "%i",  R (  0, 100));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
 
   /* The following call may write as few as 3 bytes and as many as 5.
-     It's judgment call how best to diagnose it to make the potential
+     It's a judgment call how best to diagnose it to make the potential
      problem clear.  */
-  T ( 3, "%i%i", R (1, 10), R (9, 10));   /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+  T ( 3, "%i%i", R (1, 10), R (9, 10));   /* { dg-warning "may write a terminating nul past the end|.%i. directive writing between 1 and 2 bytes into a region of size 1" } */
 
   T ( 4, "%i%i", R (10, 11), R (12, 13));   /* { dg-warning "nul past the end" } */
 
@@ -187,7 +187,7 @@  void test_sprintf_chk_range_schar (signed char *a)
   T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 10));  /* { dg-warning "may write a terminating nul past the end" } */
   T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
   T ( 6, "%i_%i_%i", R (0, 10), R (0, 9), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
-  T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning "may write a terminating nul past the end|.%i. directive writing between 1 and 2 bytes into a region of size 1" } */
 }
 
 void test_sprintf_chk_range_uchar (unsigned char *a, unsigned char *b)
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
index 9fff2ac..77b7fa5 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
@@ -1,33 +1,85 @@ 
 /* { dg-do compile } */
-/* { dg-options "-Wformat -Wformat-length=1 -fdiagnostics-show-caret -ftrack-macro-expansion=0" } */
+/* { dg-options "-Wformat -Wformat-length=1 -Wno-format-extra-args -fdiagnostics-show-caret -ftrack-macro-expansion=0" } */
 
 extern int sprintf (char*, const char*, ...);
 
-char dst [8];
+char dst [3];
+
+extern int iarg[2];
 
 void test (void)
 {
-  sprintf (dst + 7, "%-s", "1");
-  /* { dg-warning "writing a terminating nul past the end of the destination" "" { target *-*-*-* } 10 }
-     { dg-message "format output 2 bytes into a destination of size 1" "" { target *-*-*-* } 10 }
+  /* Verify thet the caret points to the (invisible) nul character
+     at the end of the format string (i.e., its closing quote).
+     The redundant argument is there to get around GCC bug 77799.  */
+  sprintf (dst + 2, "1", 0);
+  /* { dg-warning "writing a terminating nul past the end of the destination" "warning" { target *-*-*-* } .-1 }
+     { dg-message "format output 2 bytes into a destination of size 1" "note" { target *-*-*-* } .-2 }
      { dg-begin-multiline-output "" }
-   sprintf (dst + 7, "%-s", "1");
-                      ~~^~
+   sprintf (dst + 2, "1", 0);
+                      ~^
      { dg-end-multiline-output "" }
      { dg-begin-multiline-output "" }
-   sprintf (dst + 7, "%-s", "1");
+   sprintf (dst + 2, "1", 0);
+   ^~~~~~~~~~~~~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+
+
+  /* Verify thet the caret points at the format character written past
+     the end of the destination.  */
+  sprintf (dst, "1234", 0);
+  /* { dg-warning "writing format character .4. at offset 3 past the end of the destination" "warning" { target *-*-*-* } .-1 }
+     { dg-message "format output 5 bytes into a destination of size 3" "note" { target *-*-*-* } .-2 }
+     { dg-begin-multiline-output "" }
+   sprintf (dst, "1234", 0);
+                     ^
+     { dg-end-multiline-output "" }
+     { dg-begin-multiline-output "" }
+   sprintf (dst, "1234", 0);
+   ^~~~~~~~~~~~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+
+
+  /* Verify thet the caret points at the format character written past
+     the end of the destination and the rest of the format string is
+     underlined.  */
+  sprintf (dst, "12345", 0);
+  /* { dg-warning "writing format character .4. at offset 3 past the end of the destination" "warning" { target *-*-*-* } .-1 }
+     { dg-message "format output 6 bytes into a destination of size 3" "note" { target *-*-*-* } .-2 }
+     { dg-begin-multiline-output "" }
+   sprintf (dst, "12345", 0);
+                     ^~
+     { dg-end-multiline-output "" }
+     { dg-begin-multiline-output "" }
+   sprintf (dst, "12345", 0);
+   ^~~~~~~~~~~~~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+
+
+  /* Same as above but with a directive.  The minus flag is used to
+     get around GCC bug 77671.  */
+  sprintf (dst + 2, "%-s", "1");
+  /* { dg-warning "writing a terminating nul past the end of the destination" "warning" { target *-*-*-* } .-1 }
+     { dg-message "format output 2 bytes into a destination of size 1" "note" { target *-*-*-* } .-2 }
+     { dg-begin-multiline-output "" }
+   sprintf (dst + 2, "%-s", "1");
+                      ~~~^
+     { dg-end-multiline-output "" }
+     { dg-begin-multiline-output "" }
+   sprintf (dst + 2, "%-s", "1");
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    { dg-end-multiline-output "" } */
 
-  sprintf (dst + 7, "%-s", "abcd");
-  /* { dg-warning ".%-s. directive writing 4 bytes into a region of size 1" "" { target *-*-*-* } 22 }
-     { dg-message "format output 5 bytes into a destination of size 1" "" { target *-*-*-* } 22 }
+
+  sprintf (dst + 2, "%-s", "abcd");
+  /* { dg-warning ".%-s. directive writing 4 bytes into a region of size 1" "warning" { target *-*-*-* } .-1 }
+     { dg-message "format output 5 bytes into a destination of size 1" "note" { target *-*-*-* } .-2 }
      { dg-begin-multiline-output "" }
-   sprintf (dst + 7, "%-s", "abcd");
+   sprintf (dst + 2, "%-s", "abcd");
                       ^~~   ~~~~~~
      { dg-end-multiline-output "" }
      { dg-begin-multiline-output "" }
-   sprintf (dst + 7, "%-s", "abcd");
+   sprintf (dst + 2, "%-s", "abcd");
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     { dg-end-multiline-output "" } */
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c
index 1a9df98..0cb02b7 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c
@@ -88,5 +88,5 @@  void fllong (long j, char *p)
   if (k > 999)
     return;
 
-  snprintf (p, 4, "%3llu", k);   /* { dg-bogus "" "unsigned long long" { xfail *-*-* } } */
+  snprintf (p, 4, "%3llu", k);   /* { dg-bogus "" "unsigned long long" { xfail lp64 } } */
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
index b8b332f..2650970 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
@@ -359,23 +359,27 @@  test_x (unsigned char uc, unsigned short us, unsigned ui)
 }
 
 static void __attribute__ ((noinline, noclone))
-test_a_double (void)
+test_a_double (double d)
 {
-  EQL ( 6,  7, "%a",   0.0);        /* 0x0p+0 */
-  EQL ( 6,  7, "%a",   1.0);        /* 0x8p-3 */
-  EQL ( 6,  7, "%a",   2.0);        /* 0x8p-2 */
-
+  EQL ( 6,  7, "%.0a", 0.0);        /* 0x0p+0 */
+  EQL ( 6,  7, "%.0a", 1.0);        /* 0x8p-3 */
+  EQL ( 6,  7, "%.0a", 2.0);        /* 0x8p-2 */
   EQL ( 8,  9, "%.1a", 3.0);        /* 0xc.0p-2 */
-  EQL ( 9, 10, "%.2a", 4.0);        /* 0xa.00p-1 */
+  EQL ( 9, 10, "%.2a", 4.0);        /* 0x8.00p-1 */
+  EQL (10, 11, "%.3a", 5.0);        /* 0xa.000p-1 */
+
+                                    /* d is in [ 0, -DBL_MAX ] */
+  RNG ( 6, 10, 11, "%.0a", d);      /* 0x0p+0 ... -0x2p+1023 */
+  RNG ( 6, 12, 13, "%.1a", d);      /* 0x0p+0 ... -0x2.0p+1023 */
+  RNG ( 6, 13, 14, "%.2a", d);      /* 0x0p+0 ... -0x2.00p+1023 */
 }
 
 static void __attribute__ ((noinline, noclone))
 test_a_long_double (void)
 {
-  EQL ( 6,  7, "%La",   0.0L);      /* 0x0p+0 */
-  EQL ( 6,  7, "%La",   1.0L);      /* 0x8p-3 */
-  EQL ( 6,  7, "%La",   2.0L);      /* 0x8p-2 */
-
+  EQL ( 6,  7, "%.0La", 0.0L);      /* 0x0p+0 */
+  EQL ( 6,  7, "%.0La", 1.0L);      /* 0x8p-3 */
+  EQL ( 6,  7, "%.0La", 2.0L);      /* 0x8p-2 */
   EQL ( 8,  9, "%.1La", 3.0L);      /* 0xc.0p-2 */
   EQL ( 9, 10, "%.2La", 4.0L);      /* 0xa.00p-1 */
 }
@@ -525,7 +529,7 @@  int main (void)
   test_d_i (0xdeadbeef, 0xdeadbeefL);
   test_x ('?', 0xdead, 0xdeadbeef);
 
-  test_a_double ();
+  test_a_double (0.0);
   test_e_double ();
   test_f_double ();