diff mbox

avoid using types wider than int for width and precision (PR 80364)

Message ID 324a5811-ea6c-54a9-046d-27351cf4c037@gmail.com
State New
Headers show

Commit Message

Martin Sebor April 10, 2017, 10:16 p.m. UTC
>> @@ -972,11 +972,11 @@ get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
>>  	  if (range_type == VR_RANGE)
>>  	    {
>>  	      HOST_WIDE_INT type_min
>> -		= (TYPE_UNSIGNED (type)
>> -		   ? tree_to_uhwi (TYPE_MIN_VALUE (type))
>> -		   : tree_to_shwi (TYPE_MIN_VALUE (type)));
>> +		= (TYPE_UNSIGNED (argtype)
>> +		   ? tree_to_uhwi (TYPE_MIN_VALUE (argtype))
>> +		   : tree_to_shwi (TYPE_MIN_VALUE (argtype)));
>>
>> -	      HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (type));
>> +	      HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (argtype));
>
> For the start, consider what happens if argtype is __int128_t or __uint128_t
> or unsigned int here.  For the first two, those tree_to_uhwi or tree_to_shwi
> will just ICE above (if you have VR_RANGE for those, shouldn't be hard to
> write testcase).  So likely you need to ignore range info for invalid
> args with precision greater than that of integer_type_node (i.e. INT_TYPE_SIZE).

Right.  It's too bad that tree_to_uhwi is so brittle.

>
> unsigned int is I think not UB when passed to var-args and read as int, it is just
> implementation defined how is it converted into int (I could be wrong).
> In that case (i.e. TYPE_UNSIGNED and precision equal to INT_TYPE_SIZE) I
> think you need to either handle it like you do only if the max is smaller or
> equal than INT_MAX and punt to full int range otherwise, or need to take the
> unsigned -> int conversion into account.  I think passing unsigned int arg
> bigger than __INT_MAX__ is not well defined, because that means negative
> precision in the end.

C requires the type of the argument passed to the asterisk to be
int and so, strictly speaking, passing in an argument of any other
type is undefined just as is passing in any value that's not
representable in int.  The same is true for %d and %i.  But mixing
and matching signed and unsigned is common and GCC doesn't warn
about either unless -Wformat-signedness is used, so it makes sense
to handle this case more gracefully.

>
> And, if you never change type, so type == integer_type_node, you might
> consider not passing that argument at all, the behavior is hardcoded for
> that type anyway (assumption that it fits into shwi or uhwi etc.).

I can do that.  I had initially intended the function to be more
general than that but in the end wound up only using it where int
is expected.

>
> Perhaps in addition to the type_min/type_max check you do to determine
> knownrange follow it by similar range check for integer_type_node range
> and don't set unknown to false if it is not within integer_type_node range?
> The TYPE_PRECISION <= INT_TYPE_SIZE check will be still needed not to
> trigger the ICEs.

Yes, that makes sense.

Attached is an updated version with these changes.

Thanks
Martin

Comments

Jakub Jelinek April 11, 2017, 9:55 a.m. UTC | #1
On Mon, Apr 10, 2017 at 04:16:42PM -0600, Martin Sebor wrote:
> gcc/ChangeLog:
> 
> 	PR middle-end/80364
> 	* gimple-ssa-sprintf.c (get_int_range): Remove second argument and
> 	always use the int type.
> 	(directive::set_width, directive::set_precision, format_character):
> 	Adjust.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR middle-end/80364
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-16.c: New test.

> @@ -961,10 +962,16 @@ get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
>        /* True if the argument's range cannot be determined.  */
>        bool unknown = true;
>  
> -      type = TREE_TYPE (arg);
> +      tree argtype = TREE_TYPE (arg);
>  
> +      /* Ignore invalid arguments with greater precision that that
> +	 of the expected type (e.g., in sprintf("%*i", 12LL, i)).
> +	 They will have been detected and diagnosed by -Wformat and
> +	 so it's not important to complicate this code to try to deal
> +	 with them again.  */
>        if (TREE_CODE (arg) == SSA_NAME
> -	  && TREE_CODE (type) == INTEGER_TYPE)
> +	  && TREE_CODE (argtype) == INTEGER_TYPE

I think you want
	  && INTEGRAL_TYPE_P (argtype)
here instead of that last line, the middle-end considers conversions between
integral types useless (except for bool vs. non-bool conversions if the
other type has precision != 1), so if one does:
enum E { E0 = -__INT_MAX__ - 1, E1 = 1, E2 = 2, E3 = 3, E4 = __INT_MAX__ };
  enum E e = ...;
  snprintf (... "...%.*s...", ..., e, ...);
then you could very well have there ENUMERAL_TYPE etc.

Ok for trunk with that change.

	Jakub
Martin Sebor April 11, 2017, 4:38 p.m. UTC | #2
>>        if (TREE_CODE (arg) == SSA_NAME
>> -	  && TREE_CODE (type) == INTEGER_TYPE)
>> +	  && TREE_CODE (argtype) == INTEGER_TYPE
>
> I think you want
> 	  && INTEGRAL_TYPE_P (argtype)
> here instead of that last line, the middle-end considers conversions between
> integral types useless (except for bool vs. non-bool conversions if the
> other type has precision != 1), so if one does:
> enum E { E0 = -__INT_MAX__ - 1, E1 = 1, E2 = 2, E3 = 3, E4 = __INT_MAX__ };
>   enum E e = ...;
>   snprintf (... "...%.*s...", ..., e, ...);
> then you could very well have there ENUMERAL_TYPE etc.

That's right.  I keep forgetting about this.  Thanks.

>
> Ok for trunk with that change.

I committed r246846 with an additional change to make enums work
correctly.

Martin
diff mbox

Patch

PR middle-end/80364 - sanitizer detects signed integer overflow in gimple-ssa-sprintf.c

gcc/ChangeLog:

	PR middle-end/80364
	* gimple-ssa-sprintf.c (get_int_range): Remove second argument and
	always use the int type.
	(directive::set_width, directive::set_precision, format_character):
	Adjust.

gcc/testsuite/ChangeLog:

	PR middle-end/80364
	* gcc.dg/tree-ssa/builtin-sprintf-warn-16.c: New test.

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 2474391..be3a70e 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -599,8 +599,7 @@  fmtresult::type_max_digits (tree type, int base)
 }
 
 static bool
-get_int_range (tree, tree, HOST_WIDE_INT *, HOST_WIDE_INT *,
-	       bool, HOST_WIDE_INT);
+get_int_range (tree, HOST_WIDE_INT *, HOST_WIDE_INT *, bool, HOST_WIDE_INT);
 
 /* Description of a format directive.  A directive is either a plain
    string or a conversion specification that starts with '%'.  */
@@ -674,7 +673,7 @@  struct directive
      For an indeterminate ARG set width to [0, INT_MAX].  */
   void set_width (tree arg)
   {
-    get_int_range (arg, integer_type_node, width, width + 1, true, 0);
+    get_int_range (arg, width, width + 1, true, 0);
   }
 
   /* Set both bounds of the precision range to VAL.  */
@@ -690,7 +689,7 @@  struct directive
      For an indeterminate ARG set precision to [-1, INT_MAX].  */
   void set_precision (tree arg)
   {
-    get_int_range (arg, integer_type_node, prec, prec + 1, false, -1);
+    get_int_range (arg, prec, prec + 1, false, -1);
   }
 
   /* Return true if both width and precision are known to be
@@ -927,25 +926,27 @@  build_intmax_type_nodes (tree *pintmax, tree *puintmax)
     }
 }
 
-/* Determine the range [*PMIN, *PMAX] that the expression ARG of TYPE
-   is in.  Return true when the range is a subrange of that of TYPE.
-   Whn ARG is null it is as if it had the full range of TYPE.
+/* Determine the range [*PMIN, *PMAX] that the expression ARG is
+   in and that is representable in type int.
+   Return true when the range is a subrange of that of int.
+   When ARG is null it is as if it had the full range of int.
    When ABSOLUTE is true the range reflects the absolute value of
    the argument.  When ABSOLUTE is false, negative bounds of
    the determined range are replaced with NEGBOUND.  */
 
 static bool
-get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
+get_int_range (tree arg, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
 	       bool absolute, HOST_WIDE_INT negbound)
 {
+  /* The type of the result.  */
+  const_tree type = integer_type_node;
+
   bool knownrange = false;
 
   if (!arg)
     {
-      *pmin = (TYPE_UNSIGNED (type)
-	       ? tree_to_uhwi (TYPE_MIN_VALUE (type))
-	       : tree_to_shwi (TYPE_MIN_VALUE (type)));
-      *pmax = tree_to_uhwi (TYPE_MAX_VALUE (type));
+      *pmin = tree_to_shwi (TYPE_MIN_VALUE (type));
+      *pmax = tree_to_shwi (TYPE_MAX_VALUE (type));
     }
   else if (TREE_CODE (arg) == INTEGER_CST)
     {
@@ -961,10 +962,16 @@  get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
       /* True if the argument's range cannot be determined.  */
       bool unknown = true;
 
-      type = TREE_TYPE (arg);
+      tree argtype = TREE_TYPE (arg);
 
+      /* Ignore invalid arguments with greater precision that that
+	 of the expected type (e.g., in sprintf("%*i", 12LL, i)).
+	 They will have been detected and diagnosed by -Wformat and
+	 so it's not important to complicate this code to try to deal
+	 with them again.  */
       if (TREE_CODE (arg) == SSA_NAME
-	  && TREE_CODE (type) == INTEGER_TYPE)
+	  && TREE_CODE (argtype) == INTEGER_TYPE
+	  && TYPE_PRECISION (argtype) <= TYPE_PRECISION (type))
 	{
 	  /* Try to determine the range of values of the integer argument.  */
 	  wide_int min, max;
@@ -972,27 +979,34 @@  get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
 	  if (range_type == VR_RANGE)
 	    {
 	      HOST_WIDE_INT type_min
-		= (TYPE_UNSIGNED (type)
-		   ? tree_to_uhwi (TYPE_MIN_VALUE (type))
-		   : tree_to_shwi (TYPE_MIN_VALUE (type)));
+		= (TYPE_UNSIGNED (argtype)
+		   ? tree_to_uhwi (TYPE_MIN_VALUE (argtype))
+		   : tree_to_shwi (TYPE_MIN_VALUE (argtype)));
 
-	      HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (type));
+	      HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (argtype));
 
 	      *pmin = min.to_shwi ();
 	      *pmax = max.to_shwi ();
 
-	      /* Return true if the adjusted range is a subrange of
-		 the full range of the argument's type.  */
-	      knownrange = type_min < *pmin || *pmax < type_max;
-
-	      unknown = false;
+	      if (*pmin < *pmax)
+		{
+		  /* Return true if the adjusted range is a subrange of
+		     the full range of the argument's type.  *PMAX may
+		     be less than *PMIN when the argument is unsigned
+		     and its upper bound is in excess of TYPE_MAX.  In
+		     that (invalid) case disregard the range and use that
+		     of the expected type instead.  */
+		  knownrange = type_min < *pmin || *pmax < type_max;
+
+		  unknown = false;
+		}
 	    }
 	}
 
       /* Handle an argument with an unknown range as if none had been
 	 provided.  */
       if (unknown)
-	return get_int_range (NULL_TREE, type, pmin, pmax, absolute, negbound);
+	return get_int_range (NULL_TREE, pmin, pmax, absolute, negbound);
     }
 
   /* Adjust each bound as specified by ABSOLUTE and NEGBOUND.  */
@@ -1004,6 +1018,9 @@  get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
 	    *pmin = *pmax = -*pmin;
 	  else
 	    {
+	      /* Make sure signed overlow is avoided.  */
+	      gcc_assert (*pmin != HOST_WIDE_INT_MIN);
+
 	      HOST_WIDE_INT tmp = -*pmin;
 	      *pmin = 0;
 	      if (*pmax < tmp)
@@ -1948,7 +1965,7 @@  format_character (const directive &dir, tree arg)
       res.range.min = 0;
 
       HOST_WIDE_INT min, max;
-      if (get_int_range (arg, integer_type_node, &min, &max, false, 0))
+      if (get_int_range (arg, &min, &max, false, 0))
 	{
 	  if (min == 0 && max == 0)
 	    {
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-16.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-16.c
new file mode 100644
index 0000000..0e3d82e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-16.c
@@ -0,0 +1,233 @@ 
+/* PR middle-end/80364 - sanitizer detects signed integer overflow
+   in gimple-ssa-sprintf.c
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wformat-overflow=1 -ftrack-macro-expansion=0" }
+   { dg-require-effective-target int32plus } */
+
+typedef __SIZE_TYPE__  size_t;
+typedef __WCHAR_TYPE__ wchar_t;
+
+void sink (void*);
+void* get_value (void);
+
+/* Return a random width as type T.  */
+#define W(T) *(T*)get_value ()
+
+/* Return a random precision as type T.  */
+#define P(T) *(T*)get_value ()
+
+/* Return a random value as type T.  */
+#define V(T) *(T*)get_value ()
+
+extern char buf[1];
+
+/* Test convenience macro.  */
+#define T(fmt, ...)					\
+  __builtin_sprintf (buf + 1, fmt, __VA_ARGS__);	\
+  sink (buf)
+
+typedef signed char         schar_t;
+typedef unsigned char       uchar_t;
+typedef signed short        sshort_t;
+typedef unsigned short      ushort_t;
+typedef signed int          sint_t;
+typedef unsigned int        uint_t;
+typedef signed long         slong_t;
+typedef unsigned long       ulong_t;
+typedef signed long long    sllong_t;
+typedef unsigned long long  ullong_t;
+
+#if __SIZEOF_INT128__
+typedef __int128_t          sint128_t;
+typedef __uint128_t         uint128_t;
+#else
+/* When __int128_t is not available, repeat the same tests with long long.
+   This is to avoid having to guard the tests below and to avoid making
+   the dg-warning directives conditional.  */
+typedef signed long long    sint128_t;
+typedef unsigned long long  uint128_t;
+#endif
+
+const sint128_t sint128_max
+  = (sint128_t)1 << (sizeof sint128_max * __CHAR_BIT__ - 2);
+const sint128_t uint128_max = (uint128_t)-1;
+
+void test_width_cst (void)
+{
+  T ("%*i", W (schar_t), 1);     /* { dg-warning "between 1 and 128 " } */
+  T ("%*i", W (uchar_t), 12);    /* { dg-warning "between 2 and 255 " } */
+
+  T ("%*i", W (sshort_t), 123);  /* { dg-warning "between 3 and 32768 " } */
+  T ("%*i", W (ushort_t), 1234); /* { dg-warning "between 4 and 65535 " } */
+
+  T ("%*i", W (sint_t), 12345);  /* { dg-warning "between 5 and 2147483648 " } */
+  T ("%*i", W (uint_t), 123456); /* { dg-warning "between 6 and 2147483648 " } */
+
+  /* Exercise calls with invalid arguments (to verify there is no ICE).  */
+  T ("%*li", W (slong_t), 1234567L);  /* { dg-warning "between 7 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%*li", W (ulong_t), 12345678L); /* { dg-warning "between 8 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  T ("%*lli", W (sllong_t), 123456789LL);  /* { dg-warning "between 9 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%*lli", W (ullong_t), 1234567890LL); /* { dg-warning "between 10 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  T ("%*i", W (sint128_t), 0);  /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%*i", W (uint128_t), 1); /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  {
+    extern sint128_t si128;
+    if (si128 < sint128_max / 2 || sint128_max - 8 < si128)
+      si128 = sint128_max / 2;
+
+    T ("%*i", si128, 0);  /* { dg-warning "between 1 and 2147483648 " } */
+    /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+    extern uint128_t ui128;
+    if (ui128 < uint128_max / 2 || uint128_max - 8 < ui128)
+      ui128 = uint128_max / 2;
+
+    T ("%*i", ui128, 0);  /* { dg-warning "between 1 and 2147483648 " } */
+    /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  }
+
+  T ("%*i", W (float), 2);  /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%*i", W (double), 3); /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+}
+
+void test_width_var (void)
+{
+  T ("%*i", W (schar_t), V (schar_t));     /* { dg-warning "between 1 and 128 " } */
+  T ("%*i", W (uchar_t), V (uchar_t));    /* { dg-warning "between 1 and 255 " } */
+
+  T ("%*i", W (sshort_t), V (sshort_t));  /* { dg-warning "between 1 and 32768 " } */
+  T ("%*i", W (ushort_t), V (ushort_t)); /* { dg-warning "between 1 and 65535 " } */
+
+  T ("%*i", W (sint_t), V (sint_t));  /* { dg-warning "between 1 and 2147483648 " } */
+  T ("%*i", W (uint_t), V (uint_t)); /* { dg-warning "between 1 and 2147483648 " } */
+
+  /* Exercise calls with invalid arguments (to verify there is no ICE).  */
+  T ("%*li", W (slong_t), V (slong_t));  /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%*li", W (ulong_t), V (ulong_t)); /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  T ("%*lli", W (sllong_t), V (sllong_t));  /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%*lli", W (ullong_t), V (ullong_t)); /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  T ("%*i", W (float), V (int));  /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%*i", W (double), V (int)); /* { dg-warning "between 1 and 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  {
+    /* Create an unsigned range with a lower bound greater than 1 and
+       an upper bound in excess of INT_MAX and verify that the lower
+       bound isn't used as the minimum output (since the excessive
+       upper bound wraps around zero).  It's possible to constrain
+       the upper bound on the output more, based on the upper bound
+       of the width here, but not worth the trouble.  */
+    extern unsigned w;
+    if (w < 5 || (unsigned)-1 - 7 < w)
+      w = 5;
+
+    T ("%*u", w, V (int));   /* { dg-warning "between 1 and 2147483648 " } */
+  }
+}
+
+void test_precision_cst (void)
+{
+  T ("%.*i", P (schar_t), 1);     /* { dg-warning "between 1 and 127 " } */
+  T ("%.*i", P (uchar_t), 12);    /* { dg-warning "between 2 and 255 " } */
+
+  T ("%.*i", P (sshort_t), 123);  /* { dg-warning "between 3 and 32767 " } */
+  T ("%.*i", P (ushort_t), 1234); /* { dg-warning "between 4 and 65535 " } */
+
+  T ("%.*i", P (sint_t), 12345);  /* { dg-warning "between 5 and 2147483647 " } */
+  T ("%.*i", P (uint_t), 123456); /* { dg-warning "between 6 and 2147483647 " } */
+
+  /* Exercise calls with invalid arguments (to verify there is no ICE).  */
+  T ("%.*li", P (slong_t), 1234567L);  /* { dg-warning "between 7 and 2147483647 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%.*li", P (ulong_t), 12345678L); /* { dg-warning "between 8 and 2147483647 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  T ("%.*lli", P (sllong_t), 123456789LL);  /* { dg-warning "between 9 and 2147483647 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%.*lli", P (ullong_t), 1234567890LL); /* { dg-warning "between 10 and 2147483647 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  T ("%.*i", P (sint128_t), 0);  /* { dg-warning "up to 2147483647 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%.*i", P (uint128_t), 1); /* { dg-warning "between 1 and 2147483647 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  {
+    extern sint128_t si128;
+    if (si128 < sint128_max / 2 || sint128_max - 8 < si128)
+      si128 = sint128_max / 2;
+
+    T ("%.*i", si128, 0);  /* { dg-warning "up to 2147483647 " } */
+    /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+    extern uint128_t ui128;
+    if (ui128 < uint128_max / 2 || uint128_max - 8 < ui128)
+      ui128 = uint128_max / 2;
+
+    T ("%.*i", ui128, 0);  /* { dg-warning "up to 2147483647 " } */
+    /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  }
+
+  T ("%.*i", P (float), 0);  /* { dg-warning "up to 2147483647 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%.*i", P (double), 1); /* { dg-warning "between 1 and 2147483647 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+}
+
+void test_precision_var (void)
+{
+  T ("%.*i", P (schar_t), V (schar_t));     /* { dg-warning "up to 128 " } */
+  T ("%.*i", P (uchar_t), V (uchar_t));    /* { dg-warning "up to 255 " } */
+
+  T ("%.*i", P (sshort_t), V (sshort_t));  /* { dg-warning "up to 32768 " } */
+  T ("%.*i", P (ushort_t), V (ushort_t)); /* { dg-warning "up to 65535 " } */
+
+  T ("%.*i", P (sint_t), V (sint_t));  /* { dg-warning "up to 2147483648 " } */
+  T ("%.*i", P (uint_t), V (uint_t)); /* { dg-warning "up to 2147483648 " } */
+
+  /* Exercise calls with invalid arguments (to verify there is no ICE).  */
+  T ("%.*li", P (slong_t), V (slong_t));  /* { dg-warning "up to 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%.*li", P (ulong_t), V (ulong_t)); /* { dg-warning "up to 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  T ("%.*lli", P (sllong_t), V (sllong_t));  /* { dg-warning "up to 2147483648" } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%.*lli", P (ullong_t), V (ullong_t)); /* { dg-warning "up to 2147483648" } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  T ("%.*i", P (float), V (int));  /* { dg-warning "up to 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+  T ("%.*i", P (double), V (int)); /* { dg-warning "up to 2147483648 " } */
+  /* { dg-warning "expects argument of type .int." "" { target *-*-* } .-1 } */
+
+  {
+    /* Similar to the corresponding width case, create an unsigned range
+       with a lower bound greater than 1 and an upper bound in excess of
+       INT_MAX and verify that the lower bound isn't used as the minimum
+       output (since the excessive upper bound wraps around zero).  */
+    extern unsigned p;
+    if (p < 7 || (unsigned)-1 - 9 < p)
+      p = 7;
+
+    T ("%.*u", p, V (int));   /* { dg-warning "up to 2147483647 " } */
+  }
+}