diff mbox series

[3/4] - Change sprintf to use new get_range_strlen overload

Message ID 58fbb060-152b-be69-7078-8051f7de8a7f@gmail.com
State New
Headers show
Series avoid relying on type information in get_range_strlen | expand

Commit Message

Martin Sebor Oct. 2, 2018, 4:37 p.m. UTC
[3/4] - Change sprintf to use new get_range_strlen overload

This change makes use of the new get_range_strlen() overload
in gimple-ssa-sprintf.c.  This necessitated a few changes to
the API but also enabled the removal of the flexarray member
from strlen_data_t.

This also patch restores the bool return value for the public
get_strlen_range function but with a different meaning (to
indicate whether the computed range is suitable as is to rely
on for optimization, rather than whether the argument may
refer to a flexible array member).

The changes to gimple-ssa-sprintf.c involve more indentation
adjustments than new functionality so to make the review easier
I attach gcc-99999-3-gimple-ssa-sprintf.c.diff-b with the white
space changes stripped.
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 0ce016b..51f8826 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -2004,24 +2004,37 @@ get_string_length (tree str, unsigned eltsize)
   /* Determine the length of the shortest and longest string referenced
      by STR.  Strings of unknown lengths are bounded by the sizes of
      arrays that subexpressions of STR may refer to.  Pointers that
-     aren't known to point any such arrays result in LENRANGE[1] set
-     to SIZE_MAX.  */
-  tree lenrange[2];
-  bool flexarray = get_range_strlen (str, lenrange, eltsize,
-				     /* strict = */ false);
+     aren't known to point any such arrays result in LENDATA.MAXLEN
+     set to SIZE_MAX.  */
+  strlen_data_t lendata (eltsize);
+  get_range_strlen (str, &lendata);
+
+  /* Return the default result when nothing is known about the string. */
+  if (integer_all_onesp (lendata.maxsize)
+      && integer_all_onesp (lendata.maxlen))
+    return fmtresult ();
 
-  if (lenrange [0] || lenrange [1])
-    {
   HOST_WIDE_INT min
-	= (tree_fits_uhwi_p (lenrange[0])
-	   ? tree_to_uhwi (lenrange[0])
+    = (tree_fits_uhwi_p (lendata.minlen)
+       ? tree_to_uhwi (lendata.minlen)
        : 0);
 
   HOST_WIDE_INT max
-	= (tree_fits_uhwi_p (lenrange[1])
-	   ? tree_to_uhwi (lenrange[1])
+    = (tree_fits_uhwi_p (lendata.maxsize)
+       ? tree_to_uhwi (lendata.maxsize)
        : HOST_WIDE_INT_M1U);
 
+  const bool unbounded = integer_all_onesp (lendata.maxlen);
+
+  /* Set the max/likely counters to unbounded when a minimum is known
+     but the maximum length isn't bounded.  This implies that STR is
+     a conditional expression involving a string of known length and
+     and an expression of unknown/unbounded length.  */
+  if (min
+      && (unsigned HOST_WIDE_INT)min < HOST_WIDE_INT_M1U
+      && unbounded)
+    max = HOST_WIDE_INT_M1U;
+
   /* get_range_strlen() returns the target value of SIZE_MAX for
      strings of unknown length.  Bump it up to HOST_WIDE_INT_M1U
      which may be bigger.  */
@@ -2055,12 +2068,12 @@ get_string_length (tree str, unsigned eltsize)
      of an array at the end of a struct assume that it's longer than
      the array bound says it is in case it's used as a poor man's
      flexible array member, such as in struct S { char a[4]; };  */
-      res.range.unlikely = flexarray ? HOST_WIDE_INT_MAX : res.range.max;
+  if (/* lendata.flexarray || */ unbounded)
+    res.range.unlikely = HOST_WIDE_INT_MAX;
+  else
+    res.range.unlikely = res.range.max;
 
   return res;
-    }
-
-  return fmtresult ();
 }
 
 /* Return the minimum and maximum number of characters formatted
@@ -2288,6 +2301,8 @@ format_string (const directive &dir, tree arg, vr_values *)
 	  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;
+	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.unlikely)
+	    res.range.unlikely = dir.prec[1];
 	}
       else if (slen.range.min >= target_int_max ())
 	{
@@ -2297,6 +2312,7 @@ format_string (const directive &dir, tree arg, vr_values *)
 	     empty, while at level 1 they are assumed to be one byte
 	     long.  */
 	  res.range.likely = warn_level > 1;
+	  res.range.unlikely = HOST_WIDE_INT_MAX;
 	}
       else
 	{
@@ -2306,8 +2322,6 @@ format_string (const directive &dir, tree arg, vr_values *)
 	  if (res.range.likely >= target_int_max ())
 	    res.range.likely = warn_level > 1;
 	}
-
-      res.range.unlikely = res.range.max;
     }
 
   /* Bump up the byte counters if WIDTH is greater.  */

Comments

Jeff Law Oct. 10, 2018, 10:36 p.m. UTC | #1
On 10/2/18 10:37 AM, Martin Sebor wrote:
> [3/4] - Change sprintf to use new get_range_strlen overload
> 
> This change makes use of the new get_range_strlen() overload
> in gimple-ssa-sprintf.c.  This necessitated a few changes to
> the API but also enabled the removal of the flexarray member
> from strlen_data_t.
> 
> This also patch restores the bool return value for the public
> get_strlen_range function but with a different meaning (to
> indicate whether the computed range is suitable as is to rely
> on for optimization, rather than whether the argument may
> refer to a flexible array member).
> 
> The changes to gimple-ssa-sprintf.c involve more indentation
> adjustments than new functionality so to make the review easier
> I attach gcc-99999-3-gimple-ssa-sprintf.c.diff-b with the white
> space changes stripped.
> 
> gcc-99999-3.diff
> 
> [3/4] - Change sprintf to use new get_range_strlen overload
> 
> gcc/ChangeLog:
> 
> 	* gimple-fold.c (get_range_strlen): Avoid clearing minlen after
> 	recursive call fails for COND_EXPR and GIMPLE_PHI.
> 	Set minlen to ssize_type rather than size_type.  Remove flexarray.
> 	(get_range_strlen): Return bool.
> 	(get_range_strlen): Return bool.  Clear minmaxlen[0].
> 	* gimple-fold.h (strlen_data_t): Remove member.  Update comments.
> 	(get_range_strlen): Return bool.
> 	* tree-ssa-strlen.c (maybe_diag_stxncpy_trunc): Reset lenrange[0]
> 	when maxlen is unbounded.
> 	* gimple-ssa-sprintf.c (get_string_length): Call new overload of
> 	get_range_strlen.  Adjust max, likely, and unlikely counters for
> 	strings of unbounded lengths.
This fixes most, but not all of the regressions introduced in patch #2
(pr79376 continues to regress).  However it introduces a bunch of
regressions on warn-sprintf-no-nul.c.

I wouldn't be surprised if we're losing range information.  Essentially
the sprintf bits want to say "give me whatever data you've got.  I don't
care if there's an unterminated string."   I wouldn't be surprised if
the changes to get_range_strlen are running afoul of those needs as the
changes to the sprintf bits themselves look pretty reasonable.



> @@ -2055,12 +2068,12 @@ get_string_length (tree str, unsigned eltsize)
>       of an array at the end of a struct assume that it's longer than
>       the array bound says it is in case it's used as a poor man's
>       flexible array member, such as in struct S { char a[4]; };  */
> -      res.range.unlikely = flexarray ? HOST_WIDE_INT_MAX : res.range.max;
> +  if (/* lendata.flexarray || */ unbounded)
> +    res.range.unlikely = HOST_WIDE_INT_MAX;
> +  else
> +    res.range.unlikely = res.range.max;
Please remove the commented out code if indeed it's no longer desired.


Jeff
Jeff Law Oct. 11, 2018, 8:48 p.m. UTC | #2
On 10/10/18 4:36 PM, Jeff Law wrote:
> On 10/2/18 10:37 AM, Martin Sebor wrote:
>> [3/4] - Change sprintf to use new get_range_strlen overload
>>
>> This change makes use of the new get_range_strlen() overload
>> in gimple-ssa-sprintf.c.  This necessitated a few changes to
>> the API but also enabled the removal of the flexarray member
>> from strlen_data_t.
>>
>> This also patch restores the bool return value for the public
>> get_strlen_range function but with a different meaning (to
>> indicate whether the computed range is suitable as is to rely
>> on for optimization, rather than whether the argument may
>> refer to a flexible array member).
>>
>> The changes to gimple-ssa-sprintf.c involve more indentation
>> adjustments than new functionality so to make the review easier
>> I attach gcc-99999-3-gimple-ssa-sprintf.c.diff-b with the white
>> space changes stripped.
>>
>> gcc-99999-3.diff
>>
>> [3/4] - Change sprintf to use new get_range_strlen overload
>>
>> gcc/ChangeLog:
>>
>> 	* gimple-fold.c (get_range_strlen): Avoid clearing minlen after
>> 	recursive call fails for COND_EXPR and GIMPLE_PHI.
>> 	Set minlen to ssize_type rather than size_type.  Remove flexarray.
>> 	(get_range_strlen): Return bool.
>> 	(get_range_strlen): Return bool.  Clear minmaxlen[0].
>> 	* gimple-fold.h (strlen_data_t): Remove member.  Update comments.
>> 	(get_range_strlen): Return bool.
>> 	* tree-ssa-strlen.c (maybe_diag_stxncpy_trunc): Reset lenrange[0]
>> 	when maxlen is unbounded.
>> 	* gimple-ssa-sprintf.c (get_string_length): Call new overload of
>> 	get_range_strlen.  Adjust max, likely, and unlikely counters for
>> 	strings of unbounded lengths.
> This fixes most, but not all of the regressions introduced in patch #2
> (pr79376 continues to regress).  However it introduces a bunch of
> regressions on warn-sprintf-no-nul.c.
I think the warn-sprintf-no-nul.c regressions are just a matter of
failing to set res.nonstr when get_range_strlen finds as non-terminated
string.

That just leaves pr79376 -- I think the test is bogus given the
requirement that we not optimize based on subobject boundaries.  We
can't use the subobject boundaries to set the output range of the snprintf.

Jeff
diff mbox series

Patch

[3/4] - Change sprintf to use new get_range_strlen overload

gcc/ChangeLog:

	* gimple-fold.c (get_range_strlen): Avoid clearing minlen after
	recursive call fails for COND_EXPR and GIMPLE_PHI.
	Set minlen to ssize_type rather than size_type.  Remove flexarray.
	(get_range_strlen): Return bool.
	(get_range_strlen): Return bool.  Clear minmaxlen[0].
	* gimple-fold.h (strlen_data_t): Remove member.  Update comments.
	(get_range_strlen): Return bool.
	* tree-ssa-strlen.c (maybe_diag_stxncpy_trunc): Reset lenrange[0]
	when maxlen is unbounded.
	* gimple-ssa-sprintf.c (get_string_length): Call new overload of
	get_range_strlen.  Adjust max, likely, and unlikely counters for
	strings of unbounded lengths.

gcc/testsuite/ChangeLog:

	* gcc.dg/tree-ssa/builtin-snprintf-4.c: New test.

diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index eb77065..bfa9f17 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -1353,12 +1353,6 @@  get_range_strlen_tree (tree arg, bitmap *visited, StrlenType type,
 	  /* Set the minimum size to zero since the string in
 	     the array could have zero length.  */
 	  pdata->minlen = ssize_int (0);
-
-	  if (TREE_CODE (TREE_OPERAND (arg, 0)) == COMPONENT_REF
-	      && optype == TREE_TYPE (TREE_OPERAND (arg, 0))
-	      && array_at_struct_end_p (TREE_OPERAND (arg, 0)))
-	    pdata->flexarray = true;
-
 	  tight_bound = true;
 	}
       else if (TREE_CODE (arg) == COMPONENT_REF
@@ -1370,11 +1364,7 @@  get_range_strlen_tree (tree arg, bitmap *visited, StrlenType type,
 	     optimistic if the array itself isn't NUL-terminated and
 	     the caller relies on the subsequent member to contain
 	     the NUL but that would only be considered valid if
-	     the array was the last member of a struct.
-	     Set *FLEXP to true if the array whose bound is being
-	     used is at the end of a struct.  */
-	  if (array_at_struct_end_p (arg))
-	    pdata->flexarray = true;
+	     the array was the last member of a struct.  */
 
 	  tree fld = TREE_OPERAND (arg, 1);
 
@@ -1581,12 +1571,14 @@  get_range_strlen (tree arg, bitmap *visited, StrlenType type,
 		{
 		  if (type != StringSizeRange)
 		    return false;
-		  /* Set the length range to the maximum to prevent
+		  /* Set the upper bound to the maximum to prevent
 		     it from being adjusted in the next iteration but
-		     leave the more conservative MAXSIZE determined
-		     so far alone (or leave it null if it hasn't been
-		     set yet).  */
-		  pdata->minlen = size_zero_node;
+		     leave MINLEN and the more conservative MAXSIZE
+		     determined so far alone (or leave them null if
+		     they haven't been set yet).  That the MINLEN is
+		     in fact zero can be determined from MAXLEN being
+		     unbounded but the discovered minimum is used for
+		     diagnostics.  */
 		  pdata->maxlen = build_all_ones_cst (size_type_node);
 		}
 	    return true;
@@ -1613,12 +1605,14 @@  get_range_strlen (tree arg, bitmap *visited, StrlenType type,
 	      {
 		if (type != StringSizeRange)
 		  return false;
-		  /* Set the length range to the maximum to prevent
-		     it from being adjusted in subsequent iterations
-		     but leave the more conservative MAXSIZE determined
-		     so far alone (or leave it null if it hasn't been
-		     set yet).  */
-		pdata->minlen = size_zero_node;
+		  /* Set the upper bound to the maximum to prevent
+		     it from being adjusted in the next iteration but
+		     leave MINLEN and the more conservative MAXSIZE
+		     determined so far alone (or leave them null if
+		     they haven't been set yet).  That the MINLEN is
+		     in fact zero can be determined from MAXLEN being
+		     unbounded but the discovered minimum is used for
+		     diagnostics.  */
 		pdata->maxlen = build_all_ones_cst (size_type_node);
 	      }
           }
@@ -1631,9 +1625,14 @@  get_range_strlen (tree arg, bitmap *visited, StrlenType type,
 
 /* Try to obtain the range of the lengths of the string(s) referenced
    by ARG, or the size of the largest array ARG refers to if the range
-   of lengths cannot be determined, and store all in *PDATA.  */
+   of lengths cannot be determined, and store all in *PDATA.
+   Return true if the range [PDATA->MINLEN, PDATA->MAXLEN] is suitable
+   for optimization.  Returning false means that a nonzero PDATA->MINLEN
+   doesn't reflect the true lower bound of the range  when PDATA->MAXLEN
+   is -1 (in that case, the actual range is indeterminate, i.e.,
+   [0, PTRDIFF_MAX - 2].  */
 
-void
+bool
 get_range_strlen (tree arg, strlen_data_t *pdata)
 {
   bitmap visited = NULL;
@@ -1643,9 +1642,11 @@  get_range_strlen (tree arg, strlen_data_t *pdata)
       /* On failure extend the length range to an impossible maximum
 	 (a valid MAXLEN must be less than PTRDIFF_MAX - 1.  Other
 	 members can stay unchanged regardless.  */
-      pdata->minlen = size_zero_node;
+      pdata->minlen = ssize_int (0);
       pdata->maxlen = build_all_ones_cst (size_type_node);
     }
+  else if (!pdata->minlen)
+    pdata->minlen = ssize_int (0);
 
   /* Unless it's null, leave the more conservative MAXSIZE unchanged.  */
   if (!pdata->maxsize)
@@ -1653,6 +1654,10 @@  get_range_strlen (tree arg, strlen_data_t *pdata)
 
   if (visited)
     BITMAP_FREE (visited);
+
+  /* Return true if the range [PDATA->MINLEN, PDATA->MAXLEN] is
+     the true length range.  */
+  return !integer_all_onesp (pdata->maxlen);
 }
 
 /* Determine the minimum and maximum value or string length that ARG
@@ -1680,27 +1685,25 @@  get_range_strlen (tree arg, strlen_data_t *pdata)
    ELTSIZE is 1 for normal single byte character strings, and 2 or
    4 for wide characer strings.  ELTSIZE is by default 1.  */
 
-bool
+void
 get_range_strlen (tree arg, tree minmaxlen[2], unsigned eltsize,
 		  bool strict /* = true */, tree *nonstr /* = NULL */)
 {
   strlen_data_t data (eltsize);
 
   get_range_strlen (arg, &data);
-  if (strict
-      && integer_zerop (data.minlen)
-      && integer_all_onesp (data.maxlen))
+  bool unbounded = integer_all_onesp (data.maxlen);
+
+  if (strict && unbounded)
     minmaxlen[0] = minmaxlen[1] = NULL_TREE;
   else
     {
-      minmaxlen[0] = data.minlen;
+      minmaxlen[0] = unbounded ? ssize_int (0) : data.minlen;
       minmaxlen[1] = data.maxlen;
     }
 
   if (nonstr)
     *nonstr = data.nonstr;
-
-  return data.flexarray;
 }
 
 /* Return the maximum value for ARG given TYPE (see StrlenType).
@@ -3705,9 +3708,9 @@  gimple_fold_builtin_strlen (gimple_stmt_iterator *gsi)
      or if the length or its range cannot be determined.  */
   tree nonstr;
   tree lenrange[2];
-  if (get_range_strlen (arg, lenrange, /* eltsize = */ 1,
-			/* strict = */ true, &nonstr)
-      || nonstr
+  get_range_strlen (arg, lenrange, /* eltsize = */ 1,
+		    /* strict = */ true, &nonstr);
+  if (nonstr
       || !lenrange[0] || TREE_CODE (lenrange[0]) != INTEGER_CST
       || !lenrange[1] || TREE_CODE (lenrange[1]) != INTEGER_CST
       || integer_all_onesp (lenrange[1]))
diff --git a/gcc/gimple-fold.h b/gcc/gimple-fold.h
index b086c12..5e5c24b 100644
--- a/gcc/gimple-fold.h
+++ b/gcc/gimple-fold.h
@@ -28,20 +28,30 @@  extern tree get_symbol_constant_value (tree);
 
 struct strlen_data_t
 {
-  /* [MIN, MAXSIZE, MAXLEN] is a range describing the length of
-     a string of possibly unknown length.  For a string of known
-     length the range is a constant where MIN == MAXSIZE == MAXLEN
-     holds.
-     For other strings, MIN is the length of the shortest string that
-     can be stored in the referenced object, i.e., MIN == 0.  MAXSIZE
-     is the size of the largest array referenced by the expression.
-     MAXLEN is the length of the longest sequence of non-zero bytes
-     in memory reference by the expression.  For such strings,
-     MIN <= MAXSIZE <= MAXLEN holds.  For example, given:
-       struct A { char a[7], b[9]; };
+  /* [MINLEN, MAXSIZE, MAXLEN] is a range describing the length of
+     one or more strings of possibly unknown length.  For a single
+     string of known length the range is a constant where
+     MINLEN == MAXSIZE == MAXLEN holds.
+     For other strings, MINLEN is the length of the shortest known
+     string.  MAXSIZE is the length of a string that could be stored
+     in the largest array referenced by the expression.  MAXLEN is
+     the length of the longest sequence of non-zero bytes
+     in an object referenced by the expression.  For such strings,
+     MINLEN <= MAXSIZE <= MAXLEN holds.  For example, given:
+       struct A { char a[7], b[]; };
        extern struct A *p;
        n = strlen (p->a);
-     the computed range will be [0, 6, MAXOBJSIZE].
+     the computed range will be [0, 6, ALL_ONES].
+     However, for a conditional expression involving a string
+     of known length and an array of unknown bound such as
+       n = strlen (i ? p->b : "123");
+     the range will be [3, 3, ALL_ONES].
+     MINLEN != 0 && MAXLEN == ALL_ONES indicates that MINLEN is
+     the length of the shortest known string and implies that
+     the shortest possible string referenced by the expression may
+     actually be the empty string.  This distinction is useful for
+     diagnostics.  get_range_strlen() return value distinguishes
+     between these two cases.
      As the tighter (and more optimistic) bound, MAXSIZE is suitable
      for diagnostics but not for optimization.
      As the more conservative bound, MAXLEN is intended to be used
@@ -58,19 +68,15 @@  struct strlen_data_t
   /* ELTSIZE is set to 1 for single byte character strings, and 2 or 4
      for wide characer strings.  */
   unsigned eltsize;
-  /* FLEXARRAY is true if the range of the string lengths has been
-     obtained from the upper bound of an array at the end of a struct.
-     Such an array may hold a string that's longer than its upper bound
-     due to it being used as a poor-man's flexible array member.  */
-  bool flexarray;
 
   /* Set ELTSIZE and value-initialize all other members.  */
   strlen_data_t (unsigned eltbytes)
-    : minlen (), maxlen (), maxsize (), nonstr (), eltsize (eltbytes),
-      flexarray () { }
+    : minlen (), maxlen (), maxsize (), nonstr (), eltsize (eltbytes)
+  { }
 };
 
-extern void get_range_strlen (tree, strlen_data_t *);
+extern bool get_range_strlen (tree, strlen_data_t *)
+  ATTRIBUTE_NONNULL (1) ATTRIBUTE_NONNULL (2);
 
 enum StrlenType {
   /* Compute the exact constant string length.  */
@@ -87,8 +93,9 @@  enum StrlenType {
   IntegerValue
 };
 
-extern bool get_range_strlen (tree, tree[2], unsigned = 1, bool = true,
-			      tree * = NULL);
+extern void get_range_strlen (tree, tree[2], unsigned = 1, bool = true,
+			      tree * = NULL)
+  ATTRIBUTE_NONNULL (1) ATTRIBUTE_NONNULL (2);
 extern void gimplify_and_update_call_from_tree (gimple_stmt_iterator *, tree);
 extern bool fold_stmt (gimple_stmt_iterator *);
 extern bool fold_stmt (gimple_stmt_iterator *, tree (*) (tree));
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 0ce016b..51f8826 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -2004,63 +2004,76 @@  get_string_length (tree str, unsigned eltsize)
   /* Determine the length of the shortest and longest string referenced
      by STR.  Strings of unknown lengths are bounded by the sizes of
      arrays that subexpressions of STR may refer to.  Pointers that
-     aren't known to point any such arrays result in LENRANGE[1] set
-     to SIZE_MAX.  */
-  tree lenrange[2];
-  bool flexarray = get_range_strlen (str, lenrange, eltsize,
-				     /* strict = */ false);
-
-  if (lenrange [0] || lenrange [1])
-    {
-      HOST_WIDE_INT min
-	= (tree_fits_uhwi_p (lenrange[0])
-	   ? tree_to_uhwi (lenrange[0])
-	   : 0);
-
-      HOST_WIDE_INT max
-	= (tree_fits_uhwi_p (lenrange[1])
-	   ? tree_to_uhwi (lenrange[1])
-	   : HOST_WIDE_INT_M1U);
-
-      /* get_range_strlen() returns the target value of SIZE_MAX for
-	 strings of unknown length.  Bump it up to HOST_WIDE_INT_M1U
-	 which may be bigger.  */
-      if ((unsigned HOST_WIDE_INT)min == target_size_max ())
-	min = HOST_WIDE_INT_M1U;
-      if ((unsigned HOST_WIDE_INT)max == target_size_max ())
-	max = HOST_WIDE_INT_M1U;
-
-      fmtresult res (min, max);
-
-      /* Set RES.KNOWNRANGE to true if and only if all strings referenced
-	 by STR are known to be bounded (though not necessarily by their
-	 actual length but perhaps by their maximum possible length).  */
-      if (res.range.max < target_int_max ())
-	{
-	  res.knownrange = true;
-	  /* When the the length of the longest string is known and not
-	     excessive use it as the likely length of the string(s).  */
-	  res.range.likely = res.range.max;
-	}
-      else
-	{
-	  /* When the upper bound is unknown (it can be zero or excessive)
-	     set the likely length to the greater of 1 and the length of
-	     the shortest string and reset the lower bound to zero.  */
-	  res.range.likely = res.range.min ? res.range.min : warn_level > 1;
-	  res.range.min = 0;
-	}
-
-      /* If the range of string length has been estimated from the size
-	 of an array at the end of a struct assume that it's longer than
-	 the array bound says it is in case it's used as a poor man's
-	 flexible array member, such as in struct S { char a[4]; };  */
-      res.range.unlikely = flexarray ? HOST_WIDE_INT_MAX : res.range.max;
+     aren't known to point any such arrays result in LENDATA.MAXLEN
+     set to SIZE_MAX.  */
+  strlen_data_t lendata (eltsize);
+  get_range_strlen (str, &lendata);
+
+  /* Return the default result when nothing is known about the string. */
+  if (integer_all_onesp (lendata.maxsize)
+      && integer_all_onesp (lendata.maxlen))
+    return fmtresult ();
 
-      return res;
+  HOST_WIDE_INT min
+    = (tree_fits_uhwi_p (lendata.minlen)
+       ? tree_to_uhwi (lendata.minlen)
+       : 0);
+
+  HOST_WIDE_INT max
+    = (tree_fits_uhwi_p (lendata.maxsize)
+       ? tree_to_uhwi (lendata.maxsize)
+       : HOST_WIDE_INT_M1U);
+
+  const bool unbounded = integer_all_onesp (lendata.maxlen);
+
+  /* Set the max/likely counters to unbounded when a minimum is known
+     but the maximum length isn't bounded.  This implies that STR is
+     a conditional expression involving a string of known length and
+     and an expression of unknown/unbounded length.  */
+  if (min
+      && (unsigned HOST_WIDE_INT)min < HOST_WIDE_INT_M1U
+      && unbounded)
+    max = HOST_WIDE_INT_M1U;
+
+  /* get_range_strlen() returns the target value of SIZE_MAX for
+     strings of unknown length.  Bump it up to HOST_WIDE_INT_M1U
+     which may be bigger.  */
+  if ((unsigned HOST_WIDE_INT)min == target_size_max ())
+    min = HOST_WIDE_INT_M1U;
+  if ((unsigned HOST_WIDE_INT)max == target_size_max ())
+    max = HOST_WIDE_INT_M1U;
+
+  fmtresult res (min, max);
+
+  /* Set RES.KNOWNRANGE to true if and only if all strings referenced
+     by STR are known to be bounded (though not necessarily by their
+     actual length but perhaps by their maximum possible length).  */
+  if (res.range.max < target_int_max ())
+    {
+      res.knownrange = true;
+      /* When the the length of the longest string is known and not
+	 excessive use it as the likely length of the string(s).  */
+      res.range.likely = res.range.max;
+    }
+  else
+    {
+      /* When the upper bound is unknown (it can be zero or excessive)
+	 set the likely length to the greater of 1 and the length of
+	 the shortest string and reset the lower bound to zero.  */
+      res.range.likely = res.range.min ? res.range.min : warn_level > 1;
+      res.range.min = 0;
     }
 
-  return fmtresult ();
+  /* If the range of string length has been estimated from the size
+     of an array at the end of a struct assume that it's longer than
+     the array bound says it is in case it's used as a poor man's
+     flexible array member, such as in struct S { char a[4]; };  */
+  if (/* lendata.flexarray || */ unbounded)
+    res.range.unlikely = HOST_WIDE_INT_MAX;
+  else
+    res.range.unlikely = res.range.max;
+
+  return res;
 }
 
 /* Return the minimum and maximum number of characters formatted
@@ -2288,6 +2301,8 @@  format_string (const directive &dir, tree arg, vr_values *)
 	  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;
+	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.unlikely)
+	    res.range.unlikely = dir.prec[1];
 	}
       else if (slen.range.min >= target_int_max ())
 	{
@@ -2297,6 +2312,7 @@  format_string (const directive &dir, tree arg, vr_values *)
 	     empty, while at level 1 they are assumed to be one byte
 	     long.  */
 	  res.range.likely = warn_level > 1;
+	  res.range.unlikely = HOST_WIDE_INT_MAX;
 	}
       else
 	{
@@ -2306,8 +2322,6 @@  format_string (const directive &dir, tree arg, vr_values *)
 	  if (res.range.likely >= target_int_max ())
 	    res.range.likely = warn_level > 1;
 	}
-
-      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-snprintf-4.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-4.c
new file mode 100644
index 0000000..ad9a99b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-4.c
@@ -0,0 +1,51 @@ 
+/*
+  { dg-do compile }
+  { dg-options "-O2 -Wall -fdump-tree-optimized" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void abort (void);
+extern int snprintf (char*, size_t, const char*, ...);
+
+const char s0[] = "";
+const char s1[] = "a";
+const char s2[] = "ab";
+
+extern char ax[];
+extern const char* const ptr;
+
+#define CAT(x, y)      x ## y
+#define CONCAT(x, y)   CAT (x, y)
+#define TEST           CONCAT (test_on_line_, __LINE__)
+
+#define KEEP(expr) do {				\
+    if ((expr))	{				\
+      extern void TEST (void);			\
+      TEST ();					\
+    }						\
+  } while (0)
+
+
+void test_literal (int i)
+{
+  KEEP (0 < snprintf (0, 0, "%s", i ? "" : ax));
+  KEEP (1 < snprintf (0, 0, "%s", i ? ax : "1"));
+  KEEP (2 < snprintf (0, 0, "%s", i ? "12" : ptr));
+
+  KEEP (1 > snprintf (0, 0, "%s", i ? "" : ax));
+  KEEP (1 > snprintf (0, 0, "%s", i ? ax : "1"));
+  KEEP (2 > snprintf (0, 0, "%s", i ? "12" : ptr));
+}
+
+void test_cststr (int i)
+{
+  KEEP (0 < snprintf (0, 0, "%s", i ? s0 : ax));
+  KEEP (1 < snprintf (0, 0, "%s", i ? ax : s1));
+  KEEP (2 < snprintf (0, 0, "%s", i ? s2 : ptr));
+
+  KEEP (1 > snprintf (0, 0, "%s", i ? s0 : ax));
+  KEEP (1 > snprintf (0, 0, "%s", i ? ax : s1));
+  KEEP (2 > snprintf (0, 0, "%s", i ? s2 : ptr));
+}
+
+/* { dg-final { scan-tree-dump-times "test_on_line_" 12 "optimized" } } */
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index f200f04..ba07311 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -2023,7 +2023,12 @@  maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
       if (TREE_CODE (lendata.minlen) == INTEGER_CST
 	  && TREE_CODE (lendata.maxsize) == INTEGER_CST)
 	{
-	  lenrange[0] = wi::to_wide (lendata.minlen, prec);
+	  /* When LENDATA.MAXLEN is unknown, reset LENDATA.MINLEN
+	     which stores the length of the shortest known string.  */
+	  if (integer_all_onesp (lendata.maxlen))
+	    lenrange[0] = wi::shwi (0, prec);
+	  else
+	    lenrange[0] = wi::to_wide (lendata.minlen, prec);
 	  lenrange[1] = wi::to_wide (lendata.maxsize, prec);
 	}
       else