PR middle-end/49905 - Better sanity checking on sprintf src & dest to
authorMartin Sebor <msebor@redhat.com>
Wed, 21 Sep 2016 01:39:27 +0000 (01:39 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Wed, 21 Sep 2016 01:39:27 +0000 (19:39 -0600)
gcc/ChangeLog:

PR middle-end/49905
* Makefile.in (OBJS): Add gimple-ssa-sprintf.o.
* config/linux.h (TARGET_PRINTF_POINTER_FORMAT): Redefine.
* config/linux.c (gnu_libc_printf_pointer_format): New function.
* config/sol2.h (TARGET_PRINTF_POINTER_FORMAT): Same.
* config/sol2.c (solaris_printf_pointer_format): New function.
* doc/invoke.texi (-Wformat-length, -fprintf-return-value): New
options.
* doc/tm.texi.in (TARGET_PRINTF_POINTER_FORMAT): Document.
* doc/tm.texi: Regenerate.
* gimple-fold.h (get_range_strlen): New function.
(get_maxval_strlen): Declare existing function.
* gimple-fold.c (get_range_strlen): Add arguments and compute both
maximum and minimum.
 (get_range_strlen): Define overload.
(get_maxval_strlen): Adjust.
* gimple-ssa-sprintf.c: New file and pass.
* passes.def (pass_sprintf_length): Add new pass.
* targhooks.h (default_printf_pointer_format): Declare new function.
(gnu_libc_printf_pointer_format): Same.
(solaris_libc_printf_pointer_format): Same.
* targhooks.c (default_printf_pointer_format): Define new function.
* tree-pass.h (make_pass_sprintf_length): Declare new function.
* print-tree.c: Increase buffer size.

gcc/c-family/ChangeLog:

PR middle-end/49905
* c.opt: Add -Wformat-length and -fprintf-return-value.

gcc/testsuite/ChangeLog:

PR middle-end/49905
* gcc.dg/builtin-stringop-chk-1.c: Adjust.
* gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: New test.
* gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: New test.
* gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: New test.
* gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: New test.
* gcc.dg/tree-ssa/builtin-sprintf.c: New test.
* gcc.dg/tree-ssa/builtin-sprintf-2.c: New test.

From-SVN: r240298

28 files changed:
gcc/ChangeLog
gcc/Makefile.in
gcc/c-family/ChangeLog
gcc/c-family/c.opt
gcc/config/linux.c
gcc/config/linux.h
gcc/config/sol2.c
gcc/config/sol2.h
gcc/doc/invoke.texi
gcc/doc/tm.texi
gcc/doc/tm.texi.in
gcc/gimple-fold.c
gcc/gimple-fold.h
gcc/gimple-ssa-sprintf.c [new file with mode: 0644]
gcc/passes.def
gcc/print-tree.c
gcc/target.def
gcc/targhooks.c
gcc/targhooks.h
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c [new file with mode: 0644]
gcc/tree-pass.h

index 4f2f973c1d3413f791a18801f57859a52e0321be..8ccafc548a0a9966177dfc1d8dc2b4911c59459e 100644 (file)
@@ -1,3 +1,30 @@
+2016-09-20  Martin Sebor  <msebor@redhat.com>
+
+       PR middle-end/49905
+       * Makefile.in (OBJS): Add gimple-ssa-sprintf.o.
+       * config/linux.h (TARGET_PRINTF_POINTER_FORMAT): Redefine.
+       * config/linux.c (gnu_libc_printf_pointer_format): New function.
+       * config/sol2.h (TARGET_PRINTF_POINTER_FORMAT): Same.
+       * config/sol2.c (solaris_printf_pointer_format): New function.
+       * doc/invoke.texi (-Wformat-length, -fprintf-return-value): New
+       options.
+       * doc/tm.texi.in (TARGET_PRINTF_POINTER_FORMAT): Document.
+       * doc/tm.texi: Regenerate.
+       * gimple-fold.h (get_range_strlen): New function.
+       (get_maxval_strlen): Declare existing function.
+       * gimple-fold.c (get_range_strlen): Add arguments and compute both
+       maximum and minimum.
+        (get_range_strlen): Define overload.
+       (get_maxval_strlen): Adjust.
+       * gimple-ssa-sprintf.c: New file and pass.
+       * passes.def (pass_sprintf_length): Add new pass.
+       * targhooks.h (default_printf_pointer_format): Declare new function.
+       (gnu_libc_printf_pointer_format): Same.
+       (solaris_libc_printf_pointer_format): Same.
+       * targhooks.c (default_printf_pointer_format): Define new function.
+       * tree-pass.h (make_pass_sprintf_length): Declare new function.
+       * print-tree.c: Increase buffer size.
+
 2016-09-21  Kugan Vivekanandarajah  <kuganv@linaro.org>
 
         * tree-vrp.c (get_value_range): Teach PARM_DECL to use ipa-vrp
index 332c85e2fb2e7e23283414c8abd9d72303a5cfba..69ff9fa988da9010b7b68e79964997ee3018b37d 100644 (file)
@@ -1296,6 +1296,7 @@ OBJS = \
        gimple-ssa-nonnull-compare.o \
        gimple-ssa-split-paths.o \
        gimple-ssa-strength-reduction.o \
+       gimple-ssa-sprintf.o \
        gimple-streamer-in.o \
        gimple-streamer-out.o \
        gimple-walk.o \
index 5efbf55e1a711ef8bf894ed8b9fecd5670dbb976..da3fdd43aad4202dcd909153732f9811348a578d 100644 (file)
@@ -1,3 +1,8 @@
+2016-09-20  Martin Sebor  <msebor@redhat.com>
+
+       PR middle-end/49905
+       * c.opt: Add -Wformat-length and -fprintf-return-value.
+
 2016-09-19  Bernd Edlinger  <bernd.edlinger@hotmail.de>
 
        PR c++/77434
index 6cf915d5143cef62f03a34bc0fd0585842ef5efd..d01052d44e0b3f394d2e289fc81a4a10e47b3cdd 100644 (file)
@@ -478,6 +478,11 @@ Wformat-extra-args
 C ObjC C++ ObjC++ Var(warn_format_extra_args) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
 Warn if passing too many arguments to a function for its format string.
 
+Wformat-length
+C ObjC C++ ObjC++ Warning Alias(Wformat-length=, 1, 0)
+Warn about function calls with format strings that write past the end
+of the destination region.  Same as -Wformat-length=1.
+
 Wformat-nonliteral
 C ObjC C++ ObjC++ Var(warn_format_nonliteral) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 2, 0)
 Warn about format strings that are not literals.
@@ -502,6 +507,11 @@ Wformat=
 C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 1, 0)
 Warn about printf/scanf/strftime/strfmon format string anomalies.
 
+Wformat-length=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format_length) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
+Warn about function calls with format strings that write past the end
+of the destination region.
+
 Wignored-qualifiers
 C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra)
 Warn whenever type qualifiers are ignored.
@@ -1487,6 +1497,10 @@ fpretty-templates
 C++ ObjC++ Var(flag_pretty_templates) Init(1)
 -fno-pretty-templates Do not pretty-print template specializations as the template signature followed by the arguments.
 
+fprintf-return-value
+C ObjC C++ ObjC++ LTO Optimization Var(flag_printf_return_value) Init(1)
+Treat known sprintf return values as constants.
+
 freplace-objc-classes
 ObjC ObjC++ LTO Var(flag_replace_objc_classes)
 Used in Fix-and-Continue mode to indicate that object files may be swapped in at runtime.
index 16c37683b7d85c7da1acef81033e9c7faed0a2f4..9aac38b8658d58ad707a256c505c3b3ae55c2794 100644 (file)
@@ -21,8 +21,12 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "tm.h"
+#include "tree.h"
 #include "linux-protos.h"
 
+#undef TARGET_PRINTF_POINTER_FORMAT
+#define TARGET_PRINTF_POINTER_FORMAT gnu_libc_printf_pointer_format
+
 bool
 linux_libc_has_function (enum function_class fn_class)
 {
@@ -36,3 +40,16 @@ linux_libc_has_function (enum function_class fn_class)
 
   return false;
 }
+
+/* Glibc formats pointers as if by "%zx" except for the null pointer
+   which outputs "(nil)".  It ignores the pound ('#') format flag but
+   interprets the space and plus flags the same as in the integer
+   directive.  */
+
+const char*
+gnu_libc_printf_pointer_format (tree arg, const char **flags)
+{
+  *flags = " +";
+
+  return arg && integer_zerop (arg) ? "(nil)" : "%#zx";
+}
index 9aeeb948f55040986aac7bfcedeb1f3e82cea738..3ff005b90a2acde65fc5444137647a2483c909f9 100644 (file)
@@ -208,3 +208,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TARGET_LIBC_HAS_FUNCTION linux_libc_has_function
 
 #endif
+
+/* The format string to which "%p" corresponds.  */
+#undef TARGET_PRINTF_POINTER_FORMAT
+#define TARGET_PRINTF_POINTER_FORMAT gnu_libc_printf_pointer_format
index 47b41fd14085746d64a24349f7ea34ca9a179d66..30c525acbc347877c360af9be461a85b1b8ba56d 100644 (file)
@@ -30,6 +30,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "varasm.h"
 #include "output.h"
 
+#undef TARGET_PRINTF_POINTER_FORMAT
+#define TARGET_PRINTF_POINTER_FORMAT solaris_printf_pointer_format
+
 tree solaris_pending_aligns, solaris_pending_inits, solaris_pending_finis;
 
 /* Attach any pending attributes for DECL to the list in *ATTRIBUTES.
@@ -297,3 +300,14 @@ solaris_override_options (void)
   if (!HAVE_LD_EH_FRAME_CIEV3 && !global_options_set.x_dwarf_version)
     dwarf_version = 2;
 }
+
+/* Solaris libc formats pointers as if by "%zx" with the pound ('#')
+   format flag having the same meaning as in the integer directive.  */
+
+const char*
+solaris_printf_pointer_format (tree, const char **flags)
+{
+  *flags = "#";
+
+  return "%zx";
+}
index 50f2b383a1b7007c1d7654fbcbea56c1395a7d0d..6f0270891f552e435552dfc10cd94793cde03db8 100644 (file)
@@ -440,6 +440,10 @@ along with GCC; see the file COPYING3.  If not see
 #undef TARGET_LIBC_HAS_FUNCTION
 #define TARGET_LIBC_HAS_FUNCTION default_libc_has_function
 
+/* The format string to which "%p" corresponds.  */
+#undef TARGET_LIBC_PRINTF_POINTER_FORMAT
+#define TARGET_LIBC_PRINTF_POINTER_FORMAT solaris_libc_printf_pointer_format
+
 extern GTY(()) tree solaris_pending_aligns;
 extern GTY(()) tree solaris_pending_inits;
 extern GTY(()) tree solaris_pending_finis;
index 05566ba62a0fa067cf006fb1fba04d1b291ad877..cfba069cf2bb416479de565e429ee7a258ad6f3e 100644 (file)
@@ -268,7 +268,8 @@ Objective-C and Objective-C++ Dialects}.
 -Wno-div-by-zero -Wdouble-promotion -Wduplicated-cond @gol
 -Wempty-body  -Wenum-compare -Wno-endif-labels @gol
 -Werror  -Werror=* -Wfatal-errors -Wfloat-equal  -Wformat  -Wformat=2 @gol
--Wno-format-contains-nul -Wno-format-extra-args -Wformat-nonliteral @gol
+-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=@var{n} @gol
+-Wformat-nonliteral @gol
 -Wformat-security  -Wformat-signedness  -Wformat-y2k -Wframe-address @gol
 -Wframe-larger-than=@var{len} -Wno-free-nonheap-object -Wjump-misses-init @gol
 -Wignored-qualifiers  -Wignored-attributes  -Wincompatible-pointer-types @gol
@@ -379,7 +380,7 @@ Objective-C and Objective-C++ Dialects}.
 -fno-toplevel-reorder -fno-trapping-math -fno-zero-initialized-in-bss @gol
 -fomit-frame-pointer -foptimize-sibling-calls @gol
 -fpartial-inlining -fpeel-loops -fpredictive-commoning @gol
--fprefetch-loop-arrays @gol
+-fprefetch-loop-arrays -fprintf-return-value @gol
 -fprofile-correction @gol
 -fprofile-use -fprofile-use=@var{path} -fprofile-values @gol
 -fprofile-reorder-functions @gol
@@ -3888,6 +3889,88 @@ in the case of @code{scanf} formats, this option suppresses the
 warning if the unused arguments are all pointers, since the Single
 Unix Specification says that such unused arguments are allowed.
 
+@item -Wformat-length
+@itemx -Wformat-length=@var{level}
+@opindex Wformat-length
+@opindex Wno-format-length
+Warn about calls to formatted input/output functions such as @code{sprintf}
+that might overflow the destination buffer, or about bounded functions such
+as @code{snprintf} that might result in output truncation.  When the exact
+number of bytes written by a format directive cannot be determined at
+compile-time it is estimated based on heuristics that depend on the
+@var{level} argument and on optimization.  While enabling optimization
+will in most cases improve the accuracy of the warning, it may also
+result in false positives.
+
+@table @gcctabopt
+@item -Wformat-length
+@item -Wformat-length=1
+@opindex Wformat-length
+@opindex Wno-format-length
+Level @var{1} of @option{-Wformat-length} enabled by @option{-Wformat}
+employs a conservative approach that warns only about calls that most
+likely overflow the buffer or result in output truncation.  At this
+level, numeric arguments to format directives with unknown values are
+assumed to have the value of one, and strings of unknown length to be
+empty.  Numeric arguments that are known to be bounded to a subrange
+of their type, or string arguments whose output is bounded either by
+their directive's precision or by a finite set of string literals, are
+assumed to take on the value within the range that results in the most
+bytes on output.  For example, the call to @code{sprintf} below is
+diagnosed because even with both @var{a} and @var{b} equal to zero,
+the terminating NUL character (@code{'\0'}) appended by the function
+to the destination buffer will be written past its end.  Increasing
+the size of the buffer by a single byte is sufficient to avoid the
+warning, though it may not be sufficient to avoid the overflow.
+
+@smallexample
+void f (int a, int b)
+@{
+  char buf [12];
+  sprintf (buf, "a = %i, b = %i\n", a, b);
+@}
+@end smallexample
+
+@item -Wformat-length=2
+Level @var{2} warns also about calls that might overflow the destination
+buffer or result in truncation given an argument of sufficient length
+or magnitude.  At level @var{2}, unknown numeric arguments are assumed
+to have the minimum representable value for signed types with a precision
+greater than 1, and the maximum representable value otherwise.  Unknown
+string arguments whose length cannot be assumed to be bounded either by
+the directive's precision, or by a finite set of string literals they
+may evaluate to, or the character array they may point to, are assumed
+to be 1 character long.
+
+At level @var{2}, the call in the example above is again diagnosed, but
+this time because with @var{a} equal to a 32-bit @code{INT_MIN} the first
+@code{%i} directive will write some of its digits beyond the end of
+the destination buffer.  To make the call safe regardless of the values
+of the two variables, the size of the destination buffer must be increased
+to at least 34 bytes.  GCC includes the minimum size of the buffer in
+an informational note following the warning.
+
+An alternative to increasing the size of the destination buffer is to
+constrain the range of formatted values.  The maximum length of string
+arguments can be bounded by specifying the precision in the format
+directive.  When numeric arguments of format directives can be assumed
+to be bounded by less than the precision of their type, choosing
+an appropriate length modifier to the format specifier will reduce
+the required buffer size.  For example, if @var{a} and @var{b} in the
+example above can be assumed to be within the precision of
+the @code{short int} type then using either the @code{%hi} format
+directive or casting the argument to @code{short} reduces the maximum
+required size of the buffer to 24 bytes.
+
+@smallexample
+void f (int a, int b)
+@{
+  char buf [23];
+  sprintf (buf, "a = %hi, b = %i\n", a, (short)b);
+@}
+@end smallexample
+@end table
+
 @item -Wno-format-zero-length
 @opindex Wno-format-zero-length
 @opindex Wformat-zero-length
@@ -7855,6 +7938,30 @@ dependent on the structure of loops within the source code.
 
 Disabled at level @option{-Os}.
 
+@item -fprintf-return-value
+@opindex fprintf-return-value
+Substitute constants for known return value of formatted output functions
+such as @code{sprintf}, @code{snprintf}, @code{vsprintf}, and @code{vsnprintf}
+(but not @code{printf} of @code{fprintf}).  This transformation allows GCC
+to optimize or even eliminate branches based on the known return value of
+these functions called with arguments that are either constant, or whose
+values are known to be in a range that makes determining the exact return
+value possible.  For example, both the branch and the body of the @code{if}
+statement (but not the call to @code{snprint}) can be optimized away when
+@code{i} is a 32-bit or smaller integer because the return value is guaranteed
+to be at most 8.
+
+@smallexample
+char buf[9];
+if (snprintf (buf, "%08x", i) >= sizeof buf)
+  @dots{}
+@end smallexample
+
+The @option{-fprintf-return-value} option relies on other optimizations
+and yields best results with @option{-O2}.  It works in tandem with the
+@option{-Wformat-length} option.  The @option{-fprintf-return-value}
+option is disabled by default.
+
 @item -fno-peephole
 @itemx -fno-peephole2
 @opindex fno-peephole
index 92554d686208cdda1123fa4b0a5ff0729efb637b..61d3a67c88eee412f28cd9da77aa08c1bb47923b 100644 (file)
@@ -5332,6 +5332,10 @@ In either case, it remains possible to select code-generation for the alternate
 scheme, by means of compiler command line switches.
 @end defmac
 
+@deftypefn {Target Hook} {const char*} TARGET_PRINTF_POINTER_FORMAT (tree, const char **@var{flags})
+Determine the target @code{printf} implementation format string that the most closely corresponds to the @code{%p} format directive.  The object pointed to by the @var{flags} is set to a string consisting of recognized format flags such as the @code{'#'} character.
+@end deftypefn
+
 @node Addressing Modes
 @section Addressing Modes
 @cindex addressing modes
index b1482381c9ab0ea1aed041c8955c7d21b043e3bf..fd3d65dcf3e24ef5cc288474048f6ac6fd5550b4 100644 (file)
@@ -4065,6 +4065,8 @@ In either case, it remains possible to select code-generation for the alternate
 scheme, by means of compiler command line switches.
 @end defmac
 
+@hook TARGET_PRINTF_POINTER_FORMAT
+
 @node Addressing Modes
 @section Addressing Modes
 @cindex addressing modes
index 2e0bd806987d51dacf37c72e0cbe79005c55f50c..addabb745efc73465f313d1d591aea383f7f4a13 100644 (file)
@@ -1159,21 +1159,30 @@ gimple_fold_builtin_memset (gimple_stmt_iterator *gsi, tree c, tree len)
 }
 
 
-/* Return the string length, maximum string length or maximum value of
-   ARG in LENGTH.
-   If ARG is an SSA name variable, follow its use-def chains.  If LENGTH
-   is not NULL and, for TYPE == 0, its value is not equal to the length
-   we determine or if we are unable to determine the length or value,
-   return false.  VISITED is a bitmap of visited variables.
-   TYPE is 0 if string length should be returned, 1 for maximum string
-   length and 2 for maximum value ARG can have.  */
+/* Obtain the minimum and maximum string length or minimum and maximum
+   value of ARG in LENGTH[0] and LENGTH[1], respectively.
+   If ARG is an SSA name variable, follow its use-def chains.  When
+   TYPE == 0, if LENGTH[1] is not equal to the length we determine or
+   if we are unable to determine the length or value, return False.
+   VISITED is a bitmap of visited variables.
+   TYPE is 0 if string length should be obtained, 1 for maximum string
+   length and 2 for maximum value ARG can have.
+   When FUZZY is set and the length of a string cannot be determined,
+   the function instead considers as the maximum possible length the
+   size of a character array it may refer to.  */
 
 static bool
-get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
+get_range_strlen (tree arg, tree length[2], bitmap *visited, int type,
+                 bool fuzzy)
 {
   tree var, val;
   gimple *def_stmt;
 
+  /* The minimum and maximum length.  The MAXLEN pointer stays unchanged
+     but MINLEN may be cleared during the execution of the function.  */
+  tree *minlen = length;
+  tree *const maxlen = length + 1;
+
   if (TREE_CODE (arg) != SSA_NAME)
     {
       /* We can end up with &(*iftmp_1)[0] here as well, so handle it.  */
@@ -1184,8 +1193,8 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
          tree aop0 = TREE_OPERAND (TREE_OPERAND (arg, 0), 0);
          if (TREE_CODE (aop0) == INDIRECT_REF
              && TREE_CODE (TREE_OPERAND (aop0, 0)) == SSA_NAME)
-           return get_maxval_strlen (TREE_OPERAND (aop0, 0),
-                                     length, visited, type);
+           return get_range_strlen (TREE_OPERAND (aop0, 0),
+                                    length, visited, type, fuzzy);
        }
 
       if (type == 2)
@@ -1197,26 +1206,60 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
        }
       else
        val = c_strlen (arg, 1);
+
+      if (!val && fuzzy)
+       {
+         if (TREE_CODE (arg) == ADDR_EXPR)
+           return get_range_strlen (TREE_OPERAND (arg, 0), length,
+                                    visited, type, fuzzy);
+
+         if (TREE_CODE (arg) == COMPONENT_REF
+             && TREE_CODE (TREE_TYPE (TREE_OPERAND (arg, 1))) == ARRAY_TYPE)
+           {
+             /* Use the type of the member array to determine the upper
+                bound on the length of the array.  This may be overly
+                optimistic if the array itself isn't NUL-terminated and
+                the caller relies on the subsequent member to contain
+                the NUL.  */
+             arg = TREE_OPERAND (arg, 1);
+             val = TYPE_SIZE_UNIT (TREE_TYPE (arg));
+             if (!val || integer_zerop (val))
+               return false;
+             val = fold_build2 (MINUS_EXPR, TREE_TYPE (val), val,
+                                integer_one_node);
+             /* Avoid using the array size as the minimum.  */
+             minlen = NULL;
+           }
+       }
+
       if (!val)
        return false;
 
-      if (*length)
+      if (minlen
+         && (!*minlen
+             || (type > 0
+                 && TREE_CODE (*minlen) == INTEGER_CST
+                 && TREE_CODE (val) == INTEGER_CST
+                 && tree_int_cst_lt (val, *minlen))))
+       *minlen = val;
+
+      if (*maxlen)
        {
          if (type > 0)
            {
-             if (TREE_CODE (*length) != INTEGER_CST
+             if (TREE_CODE (*maxlen) != INTEGER_CST
                  || TREE_CODE (val) != INTEGER_CST)
                return false;
 
-             if (tree_int_cst_lt (*length, val))
-               *length = val;
+             if (tree_int_cst_lt (*maxlen, val))
+               *maxlen = val;
              return true;
            }
-         else if (simple_cst_equal (val, *length) != 1)
+         else if (simple_cst_equal (val, *maxlen) != 1)
            return false;
        }
 
-      *length = val;
+      *maxlen = val;
       return true;
     }
 
@@ -1244,14 +1287,14 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
             || gimple_assign_unary_nop_p (def_stmt))
           {
             tree rhs = gimple_assign_rhs1 (def_stmt);
-            return get_maxval_strlen (rhs, length, visited, type);
+           return get_range_strlen (rhs, length, visited, type, fuzzy);
           }
        else if (gimple_assign_rhs_code (def_stmt) == COND_EXPR)
          {
            tree op2 = gimple_assign_rhs2 (def_stmt);
            tree op3 = gimple_assign_rhs3 (def_stmt);
-           return get_maxval_strlen (op2, length, visited, type)
-                  && get_maxval_strlen (op3, length, visited, type);
+           return get_range_strlen (op2, length, visited, type, fuzzy)
+             && get_range_strlen (op3, length, visited, type, fuzzy);
           }
         return false;
 
@@ -1274,8 +1317,13 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
             if (arg == gimple_phi_result (def_stmt))
               continue;
 
-            if (!get_maxval_strlen (arg, length, visited, type))
-              return false;
+           if (!get_range_strlen (arg, length, visited, type, fuzzy))
+             {
+               if (fuzzy)
+                 *maxlen = build_all_ones_cst (size_type_node);
+               else
+                 return false;
+             }
           }
         }
         return true;
@@ -1285,17 +1333,40 @@ get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
     }
 }
 
+/* Determine the minimum and maximum value or string length that ARG
+   refers to and store each in the first two elements of MINMAXLEN.
+   For expressions that point to strings of unknown lengths that are
+   character arrays, use the upper bound of the array as the maximum
+   length.  For example, given an expression like 'x ? array : "xyz"'
+   and array declared as 'char array[8]', MINMAXLEN[0] will be set
+   to 3 and MINMAXLEN[1] to 7, the longest string that could be
+   stored in array.
+*/
+
+void get_range_strlen (tree arg, tree minmaxlen[2])
+{
+  bitmap visited = NULL;
+
+  minmaxlen[0] = NULL_TREE;
+  minmaxlen[1] = NULL_TREE;
+
+  get_range_strlen (arg, minmaxlen, &visited, 1, true);
+
+  if (visited)
+    BITMAP_FREE (visited);
+}
+
 tree
 get_maxval_strlen (tree arg, int type)
 {
   bitmap visited = NULL;
-  tree len = NULL_TREE;
-  if (!get_maxval_strlen (arg, &len, &visited, type))
-    len = NULL_TREE;
+  tree len[2] = { NULL_TREE, NULL_TREE };
+  if (!get_range_strlen (arg, len, &visited, type, false))
+    len[1] = NULL_TREE;
   if (visited)
     BITMAP_FREE (visited);
 
-  return len;
+  return len[1];
 }
 
 
index f3147144029db1948be940394f9694c721ad6fe1..5add30c99bd3b577299a4e8431362d7ee09dd1e8 100644 (file)
@@ -24,6 +24,8 @@ along with GCC; see the file COPYING3.  If not see
 
 extern tree canonicalize_constructor_val (tree, tree);
 extern tree get_symbol_constant_value (tree);
+extern void get_range_strlen (tree, tree[2]);
+extern tree get_maxval_strlen (tree, int);
 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
new file mode 100644 (file)
index 0000000..0afcf68
--- /dev/null
@@ -0,0 +1,2686 @@
+/* Copyright (C) 2016 Free Software Foundation, Inc.
+   Contributed by Martin Sebor <msebor@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* This file implements the printf-return-value pass.  The pass does
+   two things: 1) it analyzes calls to formatted output functions like
+   sprintf looking for possible buffer overflows and calls to bounded
+   functions like snprintf for early truncation (and under the control
+   of the -Wformat-length option issues warnings), and 2) under the
+   control of the -fprintf-return-value option it folds the return
+   value of safe calls into constants, making it possible to eliminate
+   code that depends on the value of those constants.
+
+   For all functions (bounded or not) the pass uses the size of the
+   destination object.  That means that it will diagnose calls to
+   snprintf not on the basis of the size specified by the function's
+   second argument but rathger on the basis of the size the first
+   argument points to (if possible).  For bound-checking built-ins
+   like __builtin___snprintf_chk the pass uses the size typically
+   determined by __builtin_object_size and passed to the built-in
+   by the Glibc inline wrapper.
+
+   The pass handles all forms standard sprintf format directives,
+   including character, integer, floating point, pointer, and strings,
+   with  the standard C flags, widths, and precisions.  For integers
+   and strings it computes the length of output itself.  For floating
+   point it uses MPFR to fornmat known constants with up and down
+   rounding and uses the resulting range of output lengths.  For
+   strings it uses the length of string literals and the sizes of
+   character arrays that a character pointer may point to as a bound
+   on the longest string.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-fold.h"
+#include "gimple-pretty-print.h"
+#include "diagnostic-core.h"
+#include "fold-const.h"
+#include "gimple-iterator.h"
+#include "tree-ssa.h"
+#include "tree-object-size.h"
+#include "params.h"
+#include "tree-cfg.h"
+#include "calls.h"
+#include "cfgloop.h"
+#include "intl.h"
+
+#include "builtins.h"
+#include "stor-layout.h"
+
+#include "realmpfr.h"
+#include "target.h"
+#include "targhooks.h"
+
+#include "cpplib.h"
+#include "input.h"
+#include "toplev.h"
+#include "substring-locations.h"
+#include "diagnostic.h"
+
+namespace {
+
+const pass_data pass_data_sprintf_length = {
+  GIMPLE_PASS,             // pass type
+  "printf-return-value",   // pass name
+  OPTGROUP_NONE,           // optinfo_flags
+  TV_NONE,                 // tv_id
+  PROP_cfg,                // properties_required
+  0,                      // properties_provided
+  0,                      // properties_destroyed
+  0,                      // properties_start
+  0,                      // properties_finish
+};
+
+struct format_result;
+
+class pass_sprintf_length : public gimple_opt_pass
+{
+  bool fold_return_value;
+
+public:
+  pass_sprintf_length (gcc::context *ctxt)
+    : gimple_opt_pass (pass_data_sprintf_length, ctxt),
+    fold_return_value (false)
+  { }
+
+  opt_pass * clone () { return new pass_sprintf_length (m_ctxt); }
+
+  virtual bool gate (function *);
+
+  virtual unsigned int execute (function *);
+
+  void set_pass_param (unsigned int n, bool param)
+    {
+      gcc_assert (n == 0);
+      fold_return_value = param;
+    }
+
+  void handle_gimple_call (gimple_stmt_iterator);
+
+  struct call_info;
+  void compute_format_length (const call_info &, format_result *);
+};
+
+bool
+pass_sprintf_length::gate (function *)
+{
+  /* Run the pass iff -Warn-format-length is specified and either
+     not optimizing and the pass is being invoked early, or when
+     optimizing and the pass is being invoked during optimization
+     (i.e., "late").  */
+  return ((0 < warn_format_length || flag_printf_return_value)
+         && (0 < optimize) == fold_return_value);
+}
+
+/* The result of a call to a formatted function.  */
+
+struct format_result
+{
+  /* Number of characters written by the formatted function, exact,
+     minimum and maximum when an exact number cannot be determined.
+     Setting the minimum to HOST_WIDE_INT_MAX disables all length
+     tracking for the remainder of the format string.
+     Setting either of the other two members to HOST_WIDE_INT_MAX
+     disables the exact or maximum length tracking, respectively,
+     but continues to track the maximum.  */
+  unsigned HOST_WIDE_INT number_chars;
+  unsigned HOST_WIDE_INT number_chars_min;
+  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.
+     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.  */
+  bool bounded;
+
+  /* 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.  */
+  bool constant;
+
+  /* True if no individual directive resulted in more than 4095 bytes
+     of output (the total NUMBER_CHARS might be greater).  */
+  bool under4k;
+
+  /* True when a floating point directive has been seen in the format
+     string.  */
+  bool floating;
+
+  /* True when an intermediate result has caused a warning.  Used to
+     avoid issuing duplicate warnings while finishing the processing
+     of a call.  */
+  bool warned;
+
+  /* Preincrement the number of output characters by 1.  */
+  format_result& operator++ ()
+  {
+    return *this += 1;
+  }
+
+  /* Postincrement the number of output characters by 1.  */
+  format_result operator++ (int)
+  {
+    format_result prev (*this);
+    *this += 1;
+    return prev;
+  }
+
+  /* Increment the number of output characters by N.  */
+  format_result& operator+= (unsigned HOST_WIDE_INT n)
+  {
+    gcc_assert (n < HOST_WIDE_INT_MAX);
+
+    if (number_chars < HOST_WIDE_INT_MAX)
+      number_chars += n;
+    if (number_chars_min < HOST_WIDE_INT_MAX)
+      number_chars_min += n;
+    if (number_chars_max < HOST_WIDE_INT_MAX)
+      number_chars_max += n;
+    return *this;
+  }
+};
+
+/* Return the value of INT_MIN for the target.  */
+
+static HOST_WIDE_INT
+target_int_min ()
+{
+  static const unsigned HOST_WIDE_INT int_min
+    = 1LLU << (sizeof int_min * CHAR_BIT
+              - TYPE_PRECISION (integer_type_node) + 1);
+  return int_min;
+}
+
+/* Return the value of INT_MAX for the target.  */
+
+static unsigned HOST_WIDE_INT
+target_int_max ()
+{
+  static const unsigned HOST_WIDE_INT int_max
+    = HOST_WIDE_INT_M1U >> (sizeof int_max * CHAR_BIT
+                           - TYPE_PRECISION (integer_type_node) + 1);
+  return int_max;
+}
+
+/* Return the constant initial value of DECL if available or DECL
+   otherwise.  Same as the synonymous function in c/c-typeck.c.  */
+
+static tree
+decl_constant_value (tree decl)
+{
+  if (/* Don't change a variable array bound or initial value to a constant
+        in a place where a variable is invalid.  Note that DECL_INITIAL
+        isn't valid for a PARM_DECL.  */
+      current_function_decl != 0
+      && TREE_CODE (decl) != PARM_DECL
+      && !TREE_THIS_VOLATILE (decl)
+      && TREE_READONLY (decl)
+      && DECL_INITIAL (decl) != 0
+      && TREE_CODE (DECL_INITIAL (decl)) != ERROR_MARK
+      /* This is invalid if initial value is not constant.
+        If it has either a function call, a memory reference,
+        or a variable, then re-evaluating it could give different results.  */
+      && TREE_CONSTANT (DECL_INITIAL (decl))
+      /* Check for cases where this is sub-optimal, even though valid.  */
+      && TREE_CODE (DECL_INITIAL (decl)) != CONSTRUCTOR)
+    return DECL_INITIAL (decl);
+  return decl;
+}
+
+/* Given FORMAT, set *PLOC to the source location of the format string
+   and return the format string if it is known or null otherwise.  */
+
+static const char*
+get_format_string (tree format, location_t *ploc)
+{
+  if (VAR_P (format))
+    {
+      /* Pull out a constant value if the front end didn't.  */
+      format = decl_constant_value (format);
+      STRIP_NOPS (format);
+    }
+
+  if (integer_zerop (format))
+    {
+      /* FIXME: Diagnose null format string if it hasn't been diagnosed
+        by -Wformat (the latter diagnoses only nul pointer constants,
+        this pass can do better).  */
+      return NULL;
+    }
+
+  HOST_WIDE_INT offset = 0;
+
+  if (TREE_CODE (format) == POINTER_PLUS_EXPR)
+    {
+      tree arg0 = TREE_OPERAND (format, 0);
+      tree arg1 = TREE_OPERAND (format, 1);
+      STRIP_NOPS (arg0);
+      STRIP_NOPS (arg1);
+
+      if (TREE_CODE (arg1) != INTEGER_CST)
+       return NULL;
+
+      format = arg0;
+
+      /* POINTER_PLUS_EXPR offsets are to be interpreted signed.  */
+      if (!cst_and_fits_in_hwi (arg1))
+       return NULL;
+
+      offset = int_cst_value (arg1);
+    }
+
+  if (TREE_CODE (format) != ADDR_EXPR)
+    return NULL;
+
+  *ploc = EXPR_LOC_OR_LOC (format, input_location);
+
+  format = TREE_OPERAND (format, 0);
+
+  if (TREE_CODE (format) == ARRAY_REF
+      && tree_fits_shwi_p (TREE_OPERAND (format, 1))
+      && (offset += tree_to_shwi (TREE_OPERAND (format, 1))) >= 0)
+    format = TREE_OPERAND (format, 0);
+
+  if (offset < 0)
+    return NULL;
+
+  tree array_init;
+  tree array_size = NULL_TREE;
+
+  if (VAR_P (format)
+      && TREE_CODE (TREE_TYPE (format)) == ARRAY_TYPE
+      && (array_init = decl_constant_value (format)) != format
+      && TREE_CODE (array_init) == STRING_CST)
+    {
+      /* Extract the string constant initializer.  Note that this may
+        include a trailing NUL character that is not in the array (e.g.
+        const char a[3] = "foo";).  */
+      array_size = DECL_SIZE_UNIT (format);
+      format = array_init;
+    }
+
+  if (TREE_CODE (format) != STRING_CST)
+    return NULL;
+
+  if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format))) != char_type_node)
+    {
+      /* Wide format string.  */
+      return NULL;
+    }
+
+  const char *fmtstr = TREE_STRING_POINTER (format);
+  unsigned fmtlen = TREE_STRING_LENGTH (format);
+
+  if (array_size)
+    {
+      /* Variable length arrays can't be initialized.  */
+      gcc_assert (TREE_CODE (array_size) == INTEGER_CST);
+
+      if (tree_fits_shwi_p (array_size))
+       {
+         HOST_WIDE_INT array_size_value = tree_to_shwi (array_size);
+         if (array_size_value > 0
+             && array_size_value == (int) array_size_value
+             && fmtlen > array_size_value)
+           fmtlen = array_size_value;
+       }
+    }
+  if (offset)
+    {
+      if (offset >= fmtlen)
+       return NULL;
+
+      fmtstr += offset;
+      fmtlen -= offset;
+    }
+
+  if (fmtlen < 1 || fmtstr[--fmtlen] != 0)
+    {
+      /* FIXME: Diagnose an unterminated format string if it hasn't been
+        diagnosed by -Wformat.  Similarly to a null format pointer,
+        -Wformay diagnoses only nul pointer constants, this pass can
+        do better).  */
+      return NULL;
+    }
+
+  return fmtstr;
+}
+
+/* The format_warning_at_substring function is not used here in a way
+   that makes using attribute format viable.  Suppress the warning.  */
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
+
+/* For convenience and brevity.  */
+
+static bool
+  (* const fmtwarn) (const substring_loc &, const source_range *,
+                    const char *, int, const char *, ...)
+  = format_warning_at_substring;
+
+/* Format length modifiers.  */
+
+enum format_lengths
+{
+  FMT_LEN_none,
+  FMT_LEN_hh,    // char argument
+  FMT_LEN_h,     // short
+  FMT_LEN_l,     // long
+  FMT_LEN_ll,    // long long
+  FMT_LEN_L,     // long double (and GNU long long)
+  FMT_LEN_z,     // size_t
+  FMT_LEN_t,     // ptrdiff_t
+  FMT_LEN_j      // intmax_t
+};
+
+
+/* A minimum and maximum number of bytes.  */
+
+struct result_range
+{
+  unsigned HOST_WIDE_INT min, max;
+};
+
+/* Description of the result of conversion either of a single directive
+   or the whole format string.  */
+
+struct fmtresult
+{
+  /* The range a directive's argument is in.  */
+  tree argmin, argmax;
+
+  /* The minimum and maximum number of bytes that a directive
+     results in on output for an argument in the range above.  */
+  result_range range;
+
+  /* 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,
+     though not value range propagation).  */
+  bool constant;
+};
+
+/* Description of a conversion specification.  */
+
+struct conversion_spec
+{
+  /* A bitmap of flags, one for each character.  */
+  unsigned flags[256 / sizeof (int)];
+  /* Numeric width as in "%8x".  */
+  int width;
+  /* Numeric precision as in "%.32s".  */
+  int precision;
+
+  /* Width specified via the '*' character.  */
+  tree star_width;
+  /* Precision specified via the asterisk.  */
+  tree star_precision;
+
+  /* Length modifier.  */
+  format_lengths modifier;
+
+  /* Format specifier character.  */
+  char specifier;
+
+  /* Numeric width was given.  */
+  unsigned have_width: 1;
+  /* Numeric precision was given.  */
+  unsigned have_precision: 1;
+  /* Non-zero when certain flags should be interpreted even for a directive
+     that normally doesn't accept them (used when "%p" with flags such as
+     space or plus is interepreted as a "%x".  */
+  unsigned force_flags: 1;
+
+  /* Format conversion function that given a conversion specification
+     and an argument returns the formatting result.  */
+  fmtresult  (*fmtfunc) (const conversion_spec &, tree);
+
+  /* Return True when a the format flag CHR has been used.  */
+  bool get_flag (char chr) const
+  {
+    unsigned char c = chr & 0xff;
+    return (flags[c / (CHAR_BIT * sizeof *flags)]
+           & (1U << (c % (CHAR_BIT * sizeof *flags))));
+  }
+
+  /* Make a record of the format flag CHR having been used.  */
+  void set_flag (char chr)
+  {
+    unsigned char c = chr & 0xff;
+    flags[c / (CHAR_BIT * sizeof *flags)]
+      |= (1U << (c % (CHAR_BIT * sizeof *flags)));
+  }
+
+  /* Reset the format flag CHR.  */
+  void clear_flag (char chr)
+  {
+    unsigned char c = chr & 0xff;
+    flags[c / (CHAR_BIT * sizeof *flags)]
+      &= ~(1U << (c % (CHAR_BIT * sizeof *flags)));
+  }
+};
+
+/* Return the logarithm of X in BASE.  */
+
+static int
+ilog (unsigned HOST_WIDE_INT x, int base)
+{
+  int res = 0;
+  do
+    {
+      ++res;
+      x /= base;
+    } while (x);
+  return res;
+}
+
+/* Return the number of bytes resulting from converting into a string
+   the INTEGER_CST tree node X in BASE.  PLUS indicates whether 1 for
+   a plus sign should be added for positive numbers, and PREFIX whether
+   the length of an octal ('O') or hexadecimal ('0x') prefix should be
+   added for nonzero numbers.  Return -1 if X cannot be represented.  */
+
+static int
+tree_digits (tree x, int base, bool plus, bool prefix)
+{
+  unsigned HOST_WIDE_INT absval;
+
+  int res;
+
+  if (TYPE_UNSIGNED (TREE_TYPE (x)))
+    {
+      if (tree_fits_uhwi_p (x))
+       {
+         absval = tree_to_uhwi (x);
+         res = plus;
+       }
+      else
+       return -1;
+    }
+  else
+    {
+      if (tree_fits_shwi_p (x))
+       {
+         HOST_WIDE_INT i = tree_to_shwi (x);
+         if (i < 0)
+           {
+             absval = -i;
+             res = 1;
+           }
+         else
+           {
+             absval = i;
+             res = plus;
+           }
+       }
+      else
+       return -1;
+    }
+
+  res += ilog (absval, base);
+
+  if (prefix && absval)
+    {
+      if (base == 8)
+       res += 1;
+      else if (base == 16)
+       res += 2;
+    }
+
+  return res;
+}
+
+/* Given the formatting result described by RES and NAVAIL, the number
+   of available in the destination, return the number of bytes remaining
+   in the destination.  */
+
+static inline result_range
+bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
+{
+  result_range range;
+
+  if (HOST_WIDE_INT_MAX <= navail)
+    {
+      range.min = range.max = navail;
+      return range;
+    }
+
+  if (res.number_chars < navail)
+    {
+      range.min = range.max = navail - res.number_chars;
+    }
+  else if (res.number_chars_min < navail)
+    {
+      range.max = navail - res.number_chars_min;
+    }
+  else
+    range.max = 0;
+
+  if (res.number_chars_max < navail)
+    range.min = navail - res.number_chars_max;
+  else
+    range.min = 0;
+
+  return range;
+}
+
+/* Given the formatting result described by RES and NAVAIL, the number
+   of available in the destination, return the minimum number of bytes
+   remaining in the destination.  */
+
+static inline unsigned HOST_WIDE_INT
+min_bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
+{
+  if (HOST_WIDE_INT_MAX <= navail)
+    return navail;
+
+  if (1 < warn_format_length || res.bounded)
+    {
+      /* At level 2, or when all directives output an exact number
+        of bytes or when their arguments were bounded by known
+        ranges, use the greater of the two byte counters if it's
+        valid to compute the result.  */
+      if (res.number_chars_max < HOST_WIDE_INT_MAX)
+       navail -= res.number_chars_max;
+      else if (res.number_chars < HOST_WIDE_INT_MAX)
+       navail -= res.number_chars;
+      else if (res.number_chars_min < HOST_WIDE_INT_MAX)
+       navail -= res.number_chars_min;
+    }
+  else
+    {
+      /* At level 1 use the smaller of the byte counters to compute
+        the result.  */
+      if (res.number_chars < HOST_WIDE_INT_MAX)
+       navail -= res.number_chars;
+      else if (res.number_chars_min < HOST_WIDE_INT_MAX)
+       navail -= res.number_chars_min;
+      else if (res.number_chars_max < HOST_WIDE_INT_MAX)
+       navail -= res.number_chars_max;
+    }
+
+  if (navail > HOST_WIDE_INT_MAX)
+    navail = 0;
+
+  return navail;
+}
+
+/* Description of a call to a formatted function.  */
+
+struct pass_sprintf_length::call_info
+{
+  /* Function call statement.  */
+  gimple *callstmt;
+
+  /* Function called.  */
+  tree func;
+
+  /* Called built-in function code.  */
+  built_in_function fncode;
+
+  /* Format argument and format string extracted from it.  */
+  tree format;
+  const char *fmtstr;
+
+  /* The location of the format argument.  */
+  location_t fmtloc;
+
+  /* The destination object size for __builtin___xxx_chk functions
+     typically determined by __builtin_object_size, or -1 if unknown.  */
+  unsigned HOST_WIDE_INT objsize;
+
+  /* Number of the first variable argument.  */
+  unsigned HOST_WIDE_INT argidx;
+
+  /* True for functions like snprintf that specify the size of
+     the destination, false for others like sprintf that don't.  */
+  bool bounded;
+};
+
+/* Return the result of formatting the '%%' directive.  */
+
+static fmtresult
+format_percent (const conversion_spec &, tree)
+{
+  fmtresult res;
+  res.argmin = res.argmax = NULL_TREE;
+  res.range.min = res.range.max = 1;
+  res.bounded = res.constant = true;
+  return res;
+}
+
+
+/* Ugh.  Compute intmax_type_node and uintmax_type_node the same way
+   lto/lto-lang.c does it.  This should be available in tree.h.  */
+
+static void
+build_intmax_type_nodes (tree *pintmax, tree *puintmax)
+{
+  if (strcmp (SIZE_TYPE, "unsigned int") == 0)
+    {
+      *pintmax = integer_type_node;
+      *puintmax = unsigned_type_node;
+    }
+  else if (strcmp (SIZE_TYPE, "long unsigned int") == 0)
+    {
+      *pintmax = long_integer_type_node;
+      *puintmax = long_unsigned_type_node;
+    }
+  else if (strcmp (SIZE_TYPE, "long long unsigned int") == 0)
+    {
+      *pintmax = long_long_integer_type_node;
+      *puintmax = long_long_unsigned_type_node;
+    }
+  else
+    {
+      for (int i = 0; i < NUM_INT_N_ENTS; i++)
+       if (int_n_enabled_p[i])
+         {
+           char name[50];
+           sprintf (name, "__int%d unsigned", int_n_data[i].bitsize);
+
+           if (strcmp (name, SIZE_TYPE) == 0)
+             {
+               *pintmax = int_n_trees[i].signed_type;
+               *puintmax = int_n_trees[i].unsigned_type;
+             }
+         }
+    }
+}
+
+static fmtresult
+format_integer (const conversion_spec &, tree);
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will write on output for the
+   pointer argument ARG when non-null.  ARG may be null (for vararg
+   functions).  */
+
+static fmtresult
+format_pointer (const conversion_spec &spec, tree arg)
+{
+  fmtresult res = fmtresult ();
+
+  /* Determine the target's integer format corresponding to "%p".  */
+  const char *flags;
+  const char *pfmt = targetm.printf_pointer_format (arg, &flags);
+  if (!pfmt)
+    {
+      /* The format couldn't be determined.  */
+      res.range.min = res.range.max = HOST_WIDE_INT_M1U;
+      return res;
+    }
+
+  if (pfmt [0] == '%')
+    {
+      /* Format the pointer using the integer format string.  */
+      conversion_spec pspec = spec;
+
+      /* Clear flags that are not listed as recognized.  */
+      for (const char *pf = "+ #0"; *pf; ++pf)
+       {
+         if (!strchr (flags, *pf))
+           pspec.clear_flag (*pf);
+       }
+
+      /* Set flags that are specified in the format string.  */
+      bool flag_p = true;
+      do
+       {
+         switch (*++pfmt)
+           {
+           case '+': case ' ': case '#': case '0':
+             pspec.set_flag (*pfmt);
+             break;
+           default:
+             flag_p = false;
+           }
+       }
+      while (flag_p);
+
+      /* Set the appropriate length modifier taking care to clear
+       the one that may be set (Glibc's %p accepts but ignores all
+       the integer length modifiers).  */
+      switch (*pfmt)
+       {
+       case 'l': pspec.modifier = FMT_LEN_l; ++pfmt; break;
+       case 't': pspec.modifier = FMT_LEN_t; ++pfmt; break;
+       case 'z': pspec.modifier = FMT_LEN_z; ++pfmt; break;
+       default: pspec.modifier = FMT_LEN_none;
+       }
+
+      pspec.force_flags = 1;
+      pspec.specifier = *pfmt++;
+      gcc_assert (*pfmt == '\0');
+      return format_integer (pspec, arg);
+    }
+
+  /* The format is a plain string such as Glibc's "(nil)".  */
+  res.range.min = res.range.max = strlen (pfmt);
+  return res;
+}
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will write on output for the
+   integer argument ARG when non-null.  ARG may be null (for vararg
+   functions).  */
+
+static fmtresult
+format_integer (const conversion_spec &spec, tree arg)
+{
+  /* These are available as macros in the C and C++ front ends but,
+     sadly, not here.  */
+  static tree intmax_type_node;
+  static tree uintmax_type_node;
+
+  /* Initialize the intmax nodes above the first time through here.  */
+  if (!intmax_type_node)
+    build_intmax_type_nodes (&intmax_type_node, &uintmax_type_node);
+
+  /* Set WIDTH and PRECISION to either the values in the format
+     specification or to zero.  */
+  int width = spec.have_width ? spec.width : 0;
+  int prec = spec.have_precision ? spec.precision : 0;
+
+  if (spec.star_width)
+    width = (TREE_CODE (spec.star_width) == INTEGER_CST
+            ? tree_to_shwi (spec.star_width) : 0);
+
+  if (spec.star_precision)
+    prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
+           ? tree_to_shwi (spec.star_precision) : 0);
+
+  bool sign = spec.specifier == 'd' || spec.specifier == 'i';
+
+  /* The type of the "formal" argument expected by the directive.  */
+  tree dirtype = NULL_TREE;
+
+  /* Determine the expected type of the argument from the length
+     modifier.  */
+  switch (spec.modifier)
+    {
+    case FMT_LEN_none:
+      if (spec.specifier == 'p')
+       dirtype = ptr_type_node;
+      else
+       dirtype = sign ? integer_type_node : unsigned_type_node;
+      break;
+
+    case FMT_LEN_h:
+      dirtype = sign ? short_integer_type_node : short_unsigned_type_node;
+      break;
+
+    case FMT_LEN_hh:
+      dirtype = sign ? signed_char_type_node : unsigned_char_type_node;
+      break;
+
+    case FMT_LEN_l:
+      dirtype = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_L:
+    case FMT_LEN_ll:
+      dirtype = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_z:
+      dirtype = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_t:
+      dirtype = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_j:
+      dirtype = sign ? intmax_type_node : uintmax_type_node;
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The type of the argument to the directive, either deduced from
+     the actual non-constant argument if one is known, or from
+     the directive itself when none has been provided because it's
+     a va_list.  */
+  tree argtype = NULL_TREE;
+
+  if (!arg)
+    {
+      /* When the argument has not been provided, use the type of
+        the directive's argument as an approximation.  This will
+        result in false positives for directives like %i with
+        arguments with smaller precision (such as short or char).  */
+      argtype = dirtype;
+    }
+  else if (TREE_CODE (arg) == INTEGER_CST)
+    {
+      /* The minimum and maximum number of bytes produced by
+        the directive.  */
+      fmtresult res = fmtresult ();
+
+      /* 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;
+
+      /* Base to format the number in.  */
+      int base;
+
+      /* True when a signed conversion is preceded by a sign or space.  */
+      bool maybesign;
+
+      switch (spec.specifier)
+       {
+       case 'd':
+       case 'i':
+         /* Space is only effective for signed conversions.  */
+         maybesign = spec.get_flag (' ');
+         base = 10;
+         break;
+       case 'u':
+         maybesign = spec.force_flags ? spec.get_flag (' ') : false;
+         base = 10;
+         break;
+       case 'o':
+         maybesign = spec.force_flags ? spec.get_flag (' ') : false;
+         base = 8;
+         break;
+       case 'X':
+       case 'x':
+         maybesign = spec.force_flags ? spec.get_flag (' ') : false;
+         base = 16;
+         break;
+       default:
+         gcc_unreachable ();
+       }
+
+      /* Convert the argument to the type of the directive.  */
+      arg = fold_convert (dirtype, arg);
+
+      maybesign |= spec.get_flag ('+');
+
+      /* True when a conversion is preceded by a prefix indicating the base
+        of the argument (octal or hexadecimal).  */
+      bool maybebase = spec.get_flag ('#');
+      int len = tree_digits (arg, base, maybesign, maybebase);
+
+      if (len < prec)
+       len = prec;
+
+      if (len < width)
+       len = width;
+
+      res.range.max = len;
+      res.range.min = res.range.max;
+      res.bounded = true;
+
+      return res;
+    }
+  else if (TREE_CODE (TREE_TYPE (arg)) == INTEGER_TYPE
+          || TREE_CODE (TREE_TYPE (arg)) == POINTER_TYPE)
+    {
+      /* Determine the type of the provided non-constant argument.  */
+      if (TREE_CODE (arg) == NOP_EXPR)
+       arg = TREE_OPERAND (arg, 0);
+      else if (TREE_CODE (arg) == CONVERT_EXPR)
+       arg = TREE_OPERAND (arg, 0);
+      if (TREE_CODE (arg) == COMPONENT_REF)
+       arg = TREE_OPERAND (arg, 1);
+
+      argtype = TREE_TYPE (arg);
+    }
+  else
+    {
+      /* 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;
+    }
+
+  fmtresult res = fmtresult ();
+
+  /* Using either the range the non-constant argument is in, or its
+     type (either "formal" or actual), create a range of values that
+     constrain the length of output given the warning level.  */
+  tree argmin = NULL_TREE;
+  tree argmax = NULL_TREE;
+
+  if (arg && TREE_CODE (arg) == SSA_NAME
+      && TREE_CODE (argtype) == INTEGER_TYPE)
+    {
+      /* Try to determine the range of values of the integer argument
+        (range information is not available for pointers).  */
+      wide_int min, max;
+      enum value_range_type range_type = get_range_info (arg, &min, &max);
+      if (range_type == VR_RANGE)
+       {
+         res.argmin = build_int_cst (argtype, wi::fits_uhwi_p (min)
+                                     ? min.to_uhwi () : min.to_shwi ());
+         res.argmax = build_int_cst (argtype, wi::fits_uhwi_p (max)
+                                     ? max.to_uhwi () : max.to_shwi ());
+
+         /* For a range with a negative lower bound and a non-negative
+            upper bound, use one to determine the minimum number of bytes
+            on output and whichever of the two bounds that results in
+            the greater number of bytes on output for the upper bound.
+            For example, for ARG in the range of [-3, 123], use 123 as
+            the upper bound for %i but -3 for %u.  */
+         if (wi::neg_p (min) && !wi::neg_p (max))
+           {
+             argmin = build_int_cst (argtype, wi::fits_uhwi_p (min)
+                                     ? min.to_uhwi () : min.to_shwi ());
+
+             argmax = build_int_cst (argtype, wi::fits_uhwi_p (max)
+                                     ? max.to_uhwi () : max.to_shwi ());
+
+             int minbytes = format_integer (spec, res.argmin).range.min;
+             int maxbytes = format_integer (spec, res.argmax).range.max;
+             if (maxbytes < minbytes)
+               argmax = res.argmin;
+
+             argmin = integer_zero_node;
+           }
+         else
+           {
+             argmin = res.argmin;
+             argmax = res.argmax;
+           }
+
+         /* The argument is bounded by the range of values determined
+            by Value Range Propagation.  */
+         res.bounded = true;
+       }
+      else if (range_type == VR_ANTI_RANGE)
+       {
+         /* Handle anti-ranges if/when bug 71690 is resolved.  */
+       }
+      else if (range_type == VR_VARYING)
+       {
+         /* The argument here may be the result of promoting the actual
+            argument to int.  Try to determine the type of the actual
+            argument before promotion and  narrow down its range that
+            way.  */
+         gimple *def = SSA_NAME_DEF_STMT (arg);
+         if (gimple_code (def) == GIMPLE_ASSIGN)
+           {
+             tree_code code = gimple_assign_rhs_code (def);
+             if (code == NOP_EXPR)
+               argtype = TREE_TYPE (gimple_assign_rhs1 (def));
+           }
+       }
+    }
+
+  if (!argmin)
+    {
+      /* For an unknown argument (e.g., one passed to a vararg function)
+        or one whose value range cannot be determined, create a T_MIN
+        constant if the argument's type is signed and T_MAX otherwise,
+        and use those to compute the range of bytes that the directive
+        can output.  */
+      argmin = build_int_cst (argtype, 1);
+
+      int typeprec = TYPE_PRECISION (dirtype);
+      int argprec = TYPE_PRECISION (argtype);
+
+      if (argprec < typeprec || POINTER_TYPE_P (argtype))
+       {
+         if (TYPE_UNSIGNED (argtype))
+           argmax = build_all_ones_cst (argtype);
+         else
+           argmax = fold_build2 (LSHIFT_EXPR, argtype, integer_one_node,
+                                 build_int_cst (integer_type_node,
+                                                argprec - 1));
+       }
+      else
+       {
+         argmax = fold_build2 (LSHIFT_EXPR, dirtype, integer_one_node,
+                               build_int_cst (integer_type_node,
+                                              typeprec - 1));
+       }
+      res.argmin = argmin;
+      res.argmax = argmax;
+    }
+
+  /* Recursively compute the minimum and maximum from the known range,
+     taking care to swap them if the lower bound results in longer
+     output than the upper bound (e.g., in the range [-1, 0].  */
+  res.range.min = format_integer (spec, argmin).range.min;
+  res.range.max = format_integer (spec, argmax).range.max;
+
+  /* The result is bounded either when the argument is determined to be
+     (e.g., when it's within some range) or when the minimum and maximum
+     are the same.  That can happen here for example when the specified
+     width is as wide as the greater of MIN and MAX, as would be the case
+     with sprintf (d, "%08x", x) with a 32-bit integer x.  */
+  res.bounded |= res.range.min == res.range.max;
+
+  if (res.range.max < res.range.min)
+    {
+      unsigned HOST_WIDE_INT tmp = res.range.max;
+      res.range.max = res.range.min;
+      res.range.min = tmp;
+    }
+
+  return res;
+}
+
+/* Return the number of bytes to format using the format specifier
+   SPEC the largest value in the real floating TYPE.  */
+
+static int
+format_floating_max (tree type, char spec)
+{
+  machine_mode mode = TYPE_MODE (type);
+
+  /* IBM Extended mode.  */
+  if (MODE_COMPOSITE_P (mode))
+    mode = DFmode;
+
+  /* Get the real type format desription for the target.  */
+  const real_format *rfmt = REAL_MODE_FORMAT (mode);
+  REAL_VALUE_TYPE rv;
+
+  {
+    char buf[256];
+    get_max_float (rfmt, buf, sizeof buf);
+    real_from_string (&rv, buf);
+  }
+
+  /* Convert the GCC real value representation with the precision
+     of the real type to the mpfr_t format with the GCC default
+     round-to-nearest mode.  */
+  mpfr_t x;
+  mpfr_init2 (x, rfmt->p);
+  mpfr_from_real (x, &rv, MPFR_RNDN);
+
+  const char fmt[] = { '%', 'R', spec, '\0' };
+  int n = mpfr_snprintf (NULL, 0, fmt, x);
+  return n;
+}
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will output for any argument
+   given the WIDTH and PRECISION (extracted from SPEC).  This function
+   is used when the directive argument or its value isn't known.  */
+
+static fmtresult
+format_floating (const conversion_spec &spec, int width, int prec)
+{
+  tree type;
+  bool ldbl = false;
+
+  switch (spec.modifier)
+    {
+    case FMT_LEN_none:
+      type = double_type_node;
+      break;
+
+    case FMT_LEN_L:
+      type = long_double_type_node;
+      ldbl = true;
+      break;
+
+    case FMT_LEN_ll:
+      type = long_double_type_node;
+      ldbl = true;
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  fmtresult res = fmtresult ();
+  res.constant = false;
+
+  /* Log10 of of the maximum number of exponent digits for the type.  */
+  int logexpdigs = 2;
+
+  if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
+    {
+      /* The base in which the exponent is represented should always
+        be 2 in GCC.  */
+
+      const double log10_2 = .30102999566398119521;
+
+      /* Compute T_MAX_EXP for base 2.  */
+      int expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;
+      logexpdigs = ilog (expdigs, 10);
+    }
+
+  switch (spec.specifier)
+    {
+    case 'A':
+    case 'a':
+      {
+       /* The minimum output is "0x.p+0".  */
+       res.range.min = 6 + (0 < prec ? prec : 0);
+
+       /* 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];
+       break;
+      }
+
+    case 'E':
+    case 'e':
+      {
+       bool sign = spec.get_flag ('+') || spec.get_flag (' ');
+       /* The minimum output is "[-+]1.234567e+00" regardless
+          of the value of the actual argument.  */
+       res.range.min = (sign
+                        + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+                        + 2 /* e+ */ + 2);
+       /* The maximum output is the minimum plus sign (unless already
+          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;
+       break;
+      }
+
+    case 'F':
+    case 'f':
+      {
+       /* The minimum output is "1.234567" regardless of the value
+          of the actual argument.  */
+       res.range.min = 2 + (prec < 0 ? 6 : prec);
+
+       /* Compute the maximum just once.  */
+       static const int f_max[] = {
+         format_floating_max (double_type_node, 'f'),
+         format_floating_max (long_double_type_node, 'f')
+       };
+       res.range.max = f_max [ldbl];
+       break;
+      }
+    case 'G':
+    case 'g':
+      {
+       /* The minimum is the same as for '%F'.  */
+       res.range.min = 2 + (prec < 0 ? 6 : prec);
+
+       /* Compute the maximum just once.  */
+       static const int g_max[] = {
+         format_floating_max (double_type_node, 'g'),
+         format_floating_max (long_double_type_node, 'g')
+       };
+       res.range.max = g_max [ldbl];
+       break;
+      }
+
+    default:
+      gcc_unreachable ();
+    }
+
+  if (0 < width)
+    {
+      if (res.range.min < (unsigned)width)
+       res.range.min = width;
+      if (res.range.max < (unsigned)width)
+       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;
+  return res;
+}
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will write on output for the
+   floating argument ARG.  */
+
+static fmtresult
+format_floating (const conversion_spec &spec, tree arg)
+{
+  int width = -1;
+  int prec = -1;
+
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  fmtresult res = fmtresult ();
+  res.constant = arg && TREE_CODE (arg) == REAL_CST;
+
+  if (spec.have_width)
+    width = spec.width;
+  else if (spec.star_width)
+    {
+      if (TREE_CODE (spec.star_width) == INTEGER_CST)
+       width = tree_to_shwi (spec.star_width);
+      else
+       {
+         res.range.min = res.range.max = HOST_WIDE_INT_M1U;
+         return res;
+       }
+    }
+
+  if (spec.have_precision)
+    prec = spec.precision;
+  else if (spec.star_precision)
+    {
+      if (TREE_CODE (spec.star_precision) == INTEGER_CST)
+       prec = tree_to_shwi (spec.star_precision);
+      else
+       {
+         res.range.min = res.range.max = HOST_WIDE_INT_M1U;
+         return res;
+       }
+    }
+  else if (res.constant && TOUPPER (spec.specifier) != 'A')
+    {
+      /* Specify the precision explicitly since mpfr_sprintf defaults
+        to zero.  */
+      prec = 6;
+    }
+
+  if (res.constant)
+    {
+      /* Set up an array to easily iterate over.  */
+      unsigned HOST_WIDE_INT* const minmax[] = {
+       &res.range.min, &res.range.max
+      };
+
+      /* Get the real type format desription for the target.  */
+      const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg);
+      const real_format *rfmt = REAL_MODE_FORMAT (TYPE_MODE (TREE_TYPE (arg)));
+
+      /* Convert the GCC real value representation with the precision
+        of the real type to the mpfr_t format with the GCC default
+        round-to-nearest mode.  */
+      mpfr_t mpfrval;
+      mpfr_init2 (mpfrval, rfmt->p);
+      mpfr_from_real (mpfrval, rvp, MPFR_RNDN);
+
+      char fmtstr [40];
+      char *pfmt = fmtstr;
+      *pfmt++ = '%';
+
+      /* Append flags.  */
+      for (const char *pf = "-+ #0"; *pf; ++pf)
+       if (spec.get_flag (*pf))
+         *pfmt++ = *pf;
+
+      /* Append width when specified and precision.  */
+      if (width != -1)
+       pfmt += sprintf (pfmt, "%i", width);
+      if (prec != -1)
+       pfmt += sprintf (pfmt, ".%i", prec);
+
+      /* Append the MPFR 'R' floating type specifier (no length modifier
+        is necessary or allowed by MPFR for mpfr_t values).  */
+      *pfmt++ = 'R';
+
+      /* Save the position of the MPFR rounding specifier and skip over
+        it.  It will be set in each iteration in the loop below.  */
+      char* const rndspec = pfmt++;
+
+      /* Append the C type specifier and nul-terminate.  */
+      *pfmt++ = spec.specifier;
+      *pfmt = '\0';
+
+      for (int i = 0; i != sizeof minmax / sizeof *minmax; ++i)
+       {
+         /* Use the MPFR rounding specifier to round down in the first
+            iteration and then up.  In most but not all cases this will
+            result in the same number of bytes.  */
+         *rndspec = "DU"[i];
+
+         /* Format it and store the result in the corresponding
+            member of the result struct.  */
+         *minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval);
+       }
+
+      res.bounded = res.range.min < HOST_WIDE_INT_MAX;
+      return res;
+    }
+
+  return format_floating (spec, width, prec);
+}
+
+/* Return a FMTRESULT struct set to the lengths of the shortest and longest
+   strings referenced by the expression STR, or (-1, -1) when not known.
+   Used by the format_string function below.  */
+
+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;
+    }
+
+  if (tree slen = c_strlen (str, 1))
+    {
+      /* Simply return the length of the string.  */
+      fmtresult res;
+      res.range.min = res.range.max = tree_to_shwi (slen);
+      res.bounded = true;
+      res.constant = true;
+      return res;
+    }
+
+  /* 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];
+  get_range_strlen (str, lenrange);
+
+  if (lenrange [0] || lenrange [1])
+    {
+      fmtresult res = fmtresult ();
+
+      res.range.min = (tree_fits_uhwi_p (lenrange[0])
+                      ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length);
+      res.range.max = (tree_fits_uhwi_p (lenrange[1])
+                      ? tree_to_uhwi (lenrange[1]) : HOST_WIDE_INT_M1U);
+
+      /* Set RES.BOUNDED 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).  */
+      res.bounded = res.range.max < HOST_WIDE_INT_MAX;
+
+      /* 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;
+    }
+
+  return get_string_length (NULL_TREE);
+}
+
+/* Return the minimum and maximum number of characters formatted
+   by the '%c' and '%s' format directives and ther wide character
+   forms for the argument ARG.  ARG can be null (for functions
+   such as vsprinf).  */
+
+static fmtresult
+format_string (const conversion_spec &spec, tree arg)
+{
+  unsigned width = spec.have_width && 0 < spec.width ? spec.width : 0;
+  int prec = spec.have_precision ? spec.precision : -1;
+
+  if (spec.star_width)
+    {
+      width = (TREE_CODE (spec.star_width) == INTEGER_CST
+              ? tree_to_shwi (spec.star_width) : 0);
+      if (width > INT_MAX)
+       width = 0;
+    }
+
+  if (spec.star_precision)
+    prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
+           ? tree_to_shwi (spec.star_precision) : -1);
+
+  fmtresult res = fmtresult ();
+
+  /* The maximum number of bytes for an unknown wide character argument
+     to a "%lc" directive adjusted for precision but not field width.  */
+  const unsigned HOST_WIDE_INT max_bytes_for_unknown_wc
+    = (1 == warn_format_length ? 0 <= prec ? prec : 0
+       : 2 == warn_format_length ? 0 <= prec ? prec : 1
+       : 0 <= prec ? prec : 6 /* Longest UTF-8 sequence.  */);
+
+  /* The maximum number of bytes for an unknown string argument to either
+     a "%s" or "%ls" directive adjusted for precision but not field width.  */
+  const unsigned HOST_WIDE_INT max_bytes_for_unknown_str
+    = (1 == warn_format_length ? 0 <= prec ? prec : 0
+       : 2 == warn_format_length ? 0 <= prec ? prec : 1
+       : HOST_WIDE_INT_MAX);
+
+  if (spec.specifier == 'c')
+    {
+      if (spec.modifier == FMT_LEN_l)
+       {
+         /* Positive if the argument is a wide NUL character?  */
+         int nul = (arg && TREE_CODE (arg) == INTEGER_CST
+                    ? integer_zerop (arg) : -1);
+
+         /* A '%lc' directive is the same as '%ls' for a two element
+            wide string character with the second element of NUL, so
+            when the character is unknown the minimum number of bytes
+            is the smaller of either 0 (at level 1) or 1 (at level 2)
+            and WIDTH, and the maximum is MB_CUR_MAX in the selected
+            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;
+       }
+      else
+       {
+         /* A plain '%c' directive.  */
+         res.range.min = res.range.max = 1;
+         res.bounded = true;
+         res.constant = arg && TREE_CODE (arg) == INTEGER_CST;
+       }
+    }
+  else   /* spec.specifier == 's' */
+    {
+      /* Compute the range the argument's length can be in.  */
+      fmtresult slen = get_string_length (arg);
+      if (slen.constant)
+       {
+         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;
+
+         if (spec.modifier == FMT_LEN_l)
+           {
+             if (warn_format_length > 2)
+               {
+                 res.range.min *= 6;
+
+                 /* 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;
+               }
+             /* 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
+                (due to MB_CUR_MAX).  */
+             if (0 <= prec)
+               res.range.max = prec;
+           }
+         else
+           res.constant = true;
+
+         if (0 <= prec && (unsigned)prec < res.range.min)
+           {
+             res.range.min = prec;
+             res.range.max = prec;
+           }
+       }
+      else
+       {
+         /* For a '%s' and '%ls' directive with a non-constant string,
+            the minimum number of characters is the greater of WIDTH
+            and either 0 in mode 1 or the smaller of PRECISION and 1
+            in mode 2, and the maximum is PRECISION or -1 to disable
+            tracking.  */
+
+         if (0 <= prec)
+           {
+             if ((unsigned)prec < slen.range.min
+                 || slen.range.min >= HOST_WIDE_INT_MAX)
+               slen.range.min = prec;
+             if ((unsigned)prec < slen.range.max
+                 || slen.range.max >= HOST_WIDE_INT_MAX)
+               slen.range.max = prec;
+           }
+         else if (slen.range.min >= HOST_WIDE_INT_MAX)
+           {
+             slen.range.min = max_bytes_for_unknown_str;
+             slen.range.max = max_bytes_for_unknown_str;
+           }
+
+         res.range = slen.range;
+
+         /* The output is considered bounded when a precision has been
+            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.constant = false;
+       }
+    }
+
+  /* Adjust the lengths for field width.  */
+  if (res.range.min < width)
+    res.range.min = width;
+
+  if (res.range.max < width)
+    res.range.max = width;
+
+  /* Adjust BOUNDED if width happens to make them equal.  */
+  if (res.range.min == res.range.max && res.range.min < HOST_WIDE_INT_MAX)
+    res.bounded = true;
+
+  return res;
+}
+
+/* Compute the length of the output resulting from the conversion
+   specification SPEC with the argument ARG in a call described by INFO
+   and update the overall result of the call in *RES.  The format directive
+   corresponding to SPEC starts at CVTBEG and is CVTLEN characters long.  */
+
+static void
+format_directive (const pass_sprintf_length::call_info &info,
+                 format_result *res, const char *cvtbeg, size_t cvtlen,
+                 const conversion_spec &spec, tree arg)
+{
+  /* Offset of the beginning of the directive from the beginning
+     of the format string.  */
+  size_t offset = cvtbeg - info.fmtstr;
+
+  /* Create a location for the whole directive from the % to the format
+     specifier.  */
+  substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
+                       offset, offset, offset + cvtlen - 1);
+
+  /* Also create a location range for the argument if possible.
+     This doesn't work for integer literals or function calls.  */
+  source_range argrange;
+  source_range *pargrange;
+  if (arg && CAN_HAVE_LOCATION_P (arg))
+    {
+      argrange = EXPR_LOCATION_RANGE (arg);
+      pargrange = &argrange;
+    }
+  else
+    pargrange = NULL;
+
+  /* Bail when there is no function to compute the output length,
+     or when minimum length checking has been disabled.   */
+  if (!spec.fmtfunc || res->number_chars_min >= HOST_WIDE_INT_MAX)
+    return;
+
+  /* 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;
+
+  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;
+    }
+
+  if (fmtres.range.min >= HOST_WIDE_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
+     must always be at least one byte of space for the terminating
+     NUL that's appended after the format string has been processed.  */
+  unsigned HOST_WIDE_INT navail = min_bytes_remaining (info.objsize, *res);
+
+  if (fmtres.range.min < fmtres.range.max)
+    {
+      /* The result is a range (i.e., it's inexact).  */
+      if (!res->warned)
+       {
+         bool warned = false;
+
+         if (navail < fmtres.range.min)
+           {
+             /* The minimum directive output is longer than there is
+                room in the destination.  */
+             if (fmtres.range.min == fmtres.range.max)
+               {
+                 const char* fmtstr
+                   = (info.bounded
+                      ? G_("%<%.*s%> directive output truncated writing "
+                           "%wu bytes into a region of size %wu")
+                      : G_("%<%.*s%> directive writing %wu bytes "
+                           "into a region of size %wu"));
+                 warned = fmtwarn (dirloc, pargrange, NULL,
+                                   OPT_Wformat_length_, fmtstr,
+                                   (int)cvtlen, cvtbeg, fmtres.range.min,
+                                   navail);
+               }
+             else
+               {
+                 const char* fmtstr
+                   = (info.bounded
+                      ? G_("%<%.*s%> directive output truncated writing "
+                           "between %wu and %wu bytes into a region of "
+                           "size %wu")
+                      : G_("%<%.*s%> directive writing between %wu and "
+                           "%wu bytes into a region of size %wu"));
+                 warned = fmtwarn (dirloc, pargrange, NULL,
+                                   OPT_Wformat_length_, fmtstr,
+                                   (int)cvtlen, cvtbeg,
+                                   fmtres.range.min, fmtres.range.max, navail);
+               }
+           }
+         else if (navail < fmtres.range.max
+                  && (fmtres.bounded || 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.  */
+             if (fmtres.range.max >= HOST_WIDE_INT_MAX)
+               {
+                 const char* fmtstr
+                   = (info.bounded
+                      ? G_("%<%.*s%> directive output may be truncated "
+                           "writing %wu or more bytes a region of size %wu")
+                      : G_("%<%.*s%> directive writing %wu or more bytes "
+                           "into a region of size %wu"));
+                 warned = fmtwarn (dirloc, pargrange, NULL,
+                                   OPT_Wformat_length_, fmtstr,
+                                   (int)cvtlen, cvtbeg,
+                                   fmtres.range.min, navail);
+               }
+             else
+               {
+                 const char* fmtstr
+                   = (info.bounded
+                      ? G_("%<%.*s%> directive output may be truncated "
+                           "writing between %wu and %wu bytes into a region "
+                           "of size %wu")
+                      : G_("%<%.*s%> directive writing between %wu and %wu "
+                           "bytes into a region of size %wu"));
+                 warned = fmtwarn (dirloc, pargrange, NULL,
+                                   OPT_Wformat_length_, fmtstr,
+                                   (int)cvtlen, cvtbeg,
+                                   fmtres.range.min, fmtres.range.max,
+                                   navail);
+               }
+           }
+
+         res->warned |= warned;
+
+         if (warned && fmtres.argmin)
+           {
+             if (fmtres.argmin == fmtres.argmax)
+               inform (info.fmtloc, "directive argument %qE", fmtres.argmin);
+             else if (fmtres.bounded)
+               inform (info.fmtloc, "directive argument in the range [%E, %E]",
+                       fmtres.argmin, fmtres.argmax);
+             else
+               inform (info.fmtloc,
+                       "using the range [%qE, %qE] for directive argument",
+                       fmtres.argmin, fmtres.argmax);
+           }
+       }
+
+      /* Disable exact length checking but adjust the minimum and maximum.  */
+      res->number_chars = HOST_WIDE_INT_M1U;
+      if (res->number_chars_max < HOST_WIDE_INT_MAX
+         && fmtres.range.max < HOST_WIDE_INT_MAX)
+       res->number_chars_max += fmtres.range.max;
+
+      res->number_chars_min += fmtres.range.min;
+    }
+  else
+    {
+      if (!res->warned && 0 < fmtres.range.min && navail < fmtres.range.min)
+       {
+         const char* fmtstr
+           = (info.bounded
+              ? (1 < fmtres.range.min
+                 ? G_("%<%.*s%> directive output truncated while writing "
+                      "%wu bytes into a region of size %wu")
+                 : G_("%<%.*s%> directive output truncated while writing "
+                      "%wu byte into a region of size %wu"))
+              : (1 < fmtres.range.min
+                 ? G_("%<%.*s%> directive writing %wu bytes "
+                      "into a region of size %wu")
+                 : G_("%<%.*s%> directive writing %wu byte "
+                      "into a region of size %wu")));
+
+         res->warned = fmtwarn (dirloc, pargrange, NULL,
+                                OPT_Wformat_length_, fmtstr,
+                                (int)cvtlen, cvtbeg, fmtres.range.min,
+                                navail);
+       }
+      *res += fmtres.range.min;
+    }
+
+  /* Has the minimum directive output length exceeded the maximum
+     of 4095 bytes required to be supported?  */
+  bool minunder4k = fmtres.range.min < 4096;
+  if (!minunder4k || fmtres.range.max > 4095)
+    res->under4k = false;
+
+  if (!res->warned && 1 < warn_format_length
+      && (!minunder4k || fmtres.range.max > 4095))
+    {
+      /* The directive output may be longer than the maximum required
+        to be handled by an implementation according to 7.21.6.1, p15
+        of C11.  Warn on this only at level 2 but remember this and
+        prevent folding the return value when done.  This allows for
+        the possibility of the actual libc call failing due to ENOMEM
+        (like Glibc does under some conditions).  */
+
+      if (fmtres.range.min == fmtres.range.max)
+       res->warned = fmtwarn (dirloc, pargrange, NULL,
+                              OPT_Wformat_length_,
+                              "%<%.*s%> directive output of %wu bytes exceeds "
+                              "minimum required size of 4095",
+                              (int)cvtlen, cvtbeg, fmtres.range.min);
+      else
+       {
+         const char *fmtstr
+           = (minunder4k
+              ? G_("%<%.*s%> directive output between %qu and %wu "
+                   "bytes may exceed minimum required size of 4095")
+              : G_("%<%.*s%> directive output between %qu and %wu "
+                   "bytes exceeds minimum required size of 4095"));
+
+         res->warned = fmtwarn (dirloc, pargrange, NULL,
+                                OPT_Wformat_length_, fmtstr,
+                                (int)cvtlen, cvtbeg,
+                                fmtres.range.min, fmtres.range.max);
+       }
+    }
+
+  /* Has the minimum directive output length exceeded INT_MAX?  */
+  bool exceedmin = res->number_chars_min > target_int_max ();
+
+  if (!res->warned
+      && (exceedmin
+         || (1 < warn_format_length
+             && res->number_chars_max > target_int_max ())))
+    {
+      /* The directive output causes the total length of output
+        to exceed INT_MAX bytes.  */
+
+      if (fmtres.range.min == fmtres.range.max)
+       res->warned = fmtwarn (dirloc, pargrange, NULL,
+                              OPT_Wformat_length_,
+                              "%<%.*s%> directive output of %wu bytes causes "
+                              "result to exceed %<INT_MAX%>",
+                              (int)cvtlen, cvtbeg, fmtres.range.min);
+      else
+       {
+         const char *fmtstr
+           = (exceedmin
+              ? G_ ("%<%.*s%> directive output between %wu and %wu "
+                    "bytes causes result to exceed %<INT_MAX%>")
+              : G_ ("%<%.*s%> directive output between %wu and %wu "
+                    "bytes may cause result to exceed %<INT_MAX%>"));
+         res->warned = fmtwarn (dirloc, pargrange, NULL,
+                                OPT_Wformat_length_, fmtstr,
+                                (int)cvtlen, cvtbeg,
+                                fmtres.range.min, fmtres.range.max);
+       }
+    }
+}
+
+/* Account for the number of bytes between BEG and END (or between
+   BEG + strlen (BEG) when END is null) in the format string in a call
+   to a formatted output function described by INFO.  Reflect the count
+   in RES and issue warnings as appropriate.  */
+
+static void
+add_bytes (const pass_sprintf_length::call_info &info,
+          const char *beg, const char *end, format_result *res)
+{
+  if (res->number_chars_min >= HOST_WIDE_INT_MAX)
+    return;
+
+  /* The number of bytes to output is the number of bytes between
+     the end of the last directive and the beginning of the next
+     one if it exists, otherwise the number of characters remaining
+     in the format string plus 1 for the terminating NUL.  */
+  size_t nbytes = end ? end - beg : strlen (beg) + 1;
+
+  /* Return if there are no bytes to add at this time but there are
+     directives remaining in the format string.  */
+  if (!nbytes)
+    return;
+
+  /* Compute the range of available bytes in the destination.  There
+     must always be at least one byte left for the terminating NUL
+     that's appended after the format string has been processed.  */
+  result_range avail_range = bytes_remaining (info.objsize, *res);
+
+  /* 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.)  */
+  if (!res->warned
+      && (avail_range.max < nbytes
+         || ((res->bounded || 1 < warn_format_length)
+             && avail_range.min < nbytes)))
+    {
+      /* Set NAVAIL to the number of available bytes used to decide
+        whether or not to issue a warning below.  The exact kind of
+        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))
+       navail = avail_range.min;
+
+      /* Compute the offset of the first format character that is beyond
+        the end of the destination region and the length of the rest of
+        the format string from that point on.  */
+      unsigned HOST_WIDE_INT off
+       = (unsigned HOST_WIDE_INT)(beg - info.fmtstr) + navail;
+
+      size_t len = strlen (info.fmtstr + off);
+
+      substring_loc loc
+       (info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0,
+        off + len - !!len);
+
+      /* Is the output of the last directive the result of the argument
+        being within a range whose lower bound would fit in the buffer
+        but the upper bound would not?  If so, use the word "may" to
+        indicate that the overflow/truncation may (but need not) happen.  */
+      bool boundrange
+       = (res->number_chars_min < res->number_chars_max
+          && res->number_chars_min < info.objsize);
+
+      if (!end && (nbytes - navail) == 1)
+       {
+         /* There is room for the rest of the format string but none
+            for the terminating nul.  */
+         const char *text
+           = (info.bounded   // Snprintf and the like.
+              ? (boundrange
+                 ? G_("output may be truncated before the last format character"
+                      : "output truncated before the last format character"))
+              : (boundrange
+                 ? G_("may write a terminating nul past the end "
+                      "of the destination")
+                 : G_("writing a terminating nul past the end "
+                      "of the destination")));
+
+         res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_, text);
+       }
+      else
+       {
+         /* There isn't enough room for 1 or more characters that remain
+            to copy from the format string.  */
+         const char *text
+           = (info.bounded   // Snprintf and the like.
+              ? (boundrange
+                 ? G_("output may be truncated at or before format character "
+                      "%qc at offset %wu")
+                 : G_("output truncated at format character %qc at offset %wu"))
+              : (res->number_chars >= HOST_WIDE_INT_MAX
+                 ? G_("may write format character %#qc at offset %wu past "
+                      "the end of the destination")
+                 : G_("writing format character %#qc at offset %wu past "
+                      "the end of the destination")));
+
+         res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
+                                text, info.fmtstr[off], off);
+       }
+    }
+
+  if (res->warned && !end && info.objsize < HOST_WIDE_INT_MAX)
+    {
+      /* If a warning has been issued for buffer overflow or truncation
+        (but not otherwise) help the user figure out how big a buffer
+        they need.  */
+
+      location_t callloc = gimple_location (info.callstmt);
+
+      unsigned HOST_WIDE_INT min = res->number_chars_min;
+      unsigned HOST_WIDE_INT max = res->number_chars_max;
+      unsigned HOST_WIDE_INT exact
+       = (res->number_chars < HOST_WIDE_INT_MAX
+          ? res->number_chars : res->number_chars_min);
+
+      if (min < max && max < HOST_WIDE_INT_MAX)
+       inform (callloc,
+               "format output between %wu and %wu bytes into "
+               "a destination of size %wu",
+               min + nbytes, max + nbytes, info.objsize);
+      else
+       inform (callloc,
+               (nbytes + exact == 1
+                ? G_("format output %wu byte into a destination of size %wu")
+                : G_("format output %wu bytes into a destination of size %wu")),
+               nbytes + exact, info.objsize);
+    }
+
+  /* Add the number of bytes and then check for INT_MAX overflow.  */
+  *res += nbytes;
+
+  /* Has the minimum output length minus the terminating nul exceeded
+     INT_MAX?  */
+  bool exceedmin = (res->number_chars_min - !end) > target_int_max ();
+
+  if (!res->warned
+      && (exceedmin
+         || (1 < warn_format_length
+             && (res->number_chars_max - !end) > target_int_max ())))
+    {
+      /* The function's output exceeds INT_MAX bytes.  */
+
+      /* Set NAVAIL to the number of available bytes used to decide
+        whether or not to issue a warning below.  The exact kind of
+        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))
+       navail = avail_range.min;
+
+      /* Compute the offset of the first format character that is beyond
+        the end of the destination region and the length of the rest of
+        the format string from that point on.  */
+      unsigned HOST_WIDE_INT off = (unsigned HOST_WIDE_INT)(beg - info.fmtstr);
+      if (navail < HOST_WIDE_INT_MAX)
+       off += navail;
+
+      size_t len = strlen (info.fmtstr + off);
+
+      substring_loc loc
+       (info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0,
+        off + len - !!len);
+
+      if (res->number_chars_min == res->number_chars_max)
+       res->warned = fmtwarn (loc, NULL, NULL,
+                              OPT_Wformat_length_,
+                              "output of %wu bytes causes "
+                              "result to exceed %<INT_MAX%>",
+                              res->number_chars_min - !end);
+      else
+       {
+         const char *text
+           = (exceedmin
+              ? G_ ("output between %wu and %wu bytes causes "
+                    "result to exceed %<INT_MAX%>")
+              : G_ ("output between %wu and %wu bytes may cause "
+                    "result to exceed %<INT_MAX%>"));
+         res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
+                                text,
+                                res->number_chars_min - !end,
+                                res->number_chars_max - !end);
+       }
+    }
+}
+
+#pragma GCC diagnostic pop
+
+/* Compute the length of the output resulting from the call to a formatted
+   output function described by INFO and store the result of the call in
+   *RES.  Issue warnings for detected past the end writes.  */
+
+void
+pass_sprintf_length::compute_format_length (const call_info &info,
+                                           format_result *res)
+{
+  /* The variadic argument counter.  */
+  unsigned argno = info.argidx;
+
+  /* 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.  */
+  res->bounded = true;
+  res->constant = true;
+  res->under4k = true;
+  res->floating = false;
+  res->warned = false;
+
+  const char *pf = info.fmtstr;
+
+  for ( ; ; )
+    {
+      /* The beginning of the next format directive.  */
+      const char *dir = strchr (pf, '%');
+
+      /* Add the number of bytes between the end of the last directive
+        and either the next if one exists, or the end of the format
+        string.  */
+      add_bytes (info, pf, dir, res);
+
+      if (!dir)
+       break;
+
+      pf = dir + 1;
+
+      if (0 && *pf == 0)
+       {
+         /* Incomplete directive.  */
+         return;
+       }
+
+      conversion_spec spec = conversion_spec ();
+
+      /* POSIX numbered argument index or zero when none.  */
+      unsigned dollar = 0;
+
+      if (ISDIGIT (*pf))
+       {
+         /* This could be either a POSIX positional argument, the '0'
+            flag, or a width, depending on what follows.  Store it as
+            width and sort it out later after the next character has
+            been seen.  */
+         char *end;
+         spec.width = strtol (pf, &end, 10);
+         spec.have_width = true;
+         pf = end;
+       }
+      else if ('*' == *pf)
+       {
+         /* Similarly to the block above, this could be either a POSIX
+            positional argument or a width, depending on what follows.  */
+         if (argno < gimple_call_num_args (info.callstmt))
+           spec.star_width = gimple_call_arg (info.callstmt, argno++);
+         else
+           return;
+         ++pf;
+       }
+
+      if (*pf == '$')
+       {
+         /* Handle the POSIX dollar sign which references the 1-based
+            positional argument number.  */
+         if (spec.have_width)
+           dollar = spec.width + info.argidx;
+         else if (spec.star_width
+                  && TREE_CODE (spec.star_width) == INTEGER_CST)
+           dollar = spec.width + tree_to_shwi (spec.star_width);
+
+         /* Bail when the numbered argument is out of range (it will
+            have already been diagnosed by -Wformat).  */
+         if (dollar == 0
+             || dollar == info.argidx
+             || dollar > gimple_call_num_args (info.callstmt))
+           return;
+
+         --dollar;
+
+         spec.star_width = NULL_TREE;
+         spec.have_width = false;
+         ++pf;
+       }
+
+      if (dollar || !spec.star_width)
+       {
+         if (spec.have_width && spec.width == 0)
+           {
+             /* The '0' that has been interpreted as a width above is
+                actually a flag.  Reset HAVE_WIDTH, set the '0' flag,
+                and continue processing other flags.  */
+             spec.have_width = false;
+             spec.set_flag ('0');
+           }
+         /* When either '$' has been seen, or width has not been seen,
+            the next field is the optional flags followed by an optional
+            width.  */
+         for ( ; ; ) {
+           switch (*pf)
+             {
+             case ' ':
+             case '0':
+             case '+':
+             case '-':
+             case '#':
+               spec.set_flag (*pf++);
+               break;
+
+             default:
+               goto start_width;
+             }
+         }
+
+       start_width:
+         if (ISDIGIT (*pf))
+           {
+             char *end;
+             spec.width = strtol (pf, &end, 10);
+             spec.have_width = true;
+             pf = end;
+           }
+         else if ('*' == *pf)
+           {
+             spec.star_width = gimple_call_arg (info.callstmt, argno++);
+             ++pf;
+           }
+         else if ('\'' == *pf)
+           {
+             /* The POSIX apostrophe indicating a numeric grouping
+                in the current locale.  Even though it's possible to
+                estimate the upper bound on the size of the output
+                based on the number of digits it probably isn't worth
+                continuing.  */
+             return;
+           }
+       }
+
+      if ('.' == *pf)
+       {
+         ++pf;
+
+         if (ISDIGIT (*pf))
+           {
+             char *end;
+             spec.precision = strtol (pf, &end, 10);
+             spec.have_precision = true;
+             pf = end;
+           }
+         else if ('*' == *pf)
+           {
+             spec.star_precision = gimple_call_arg (info.callstmt, argno++);
+             ++pf;
+           }
+         else
+           return;
+       }
+
+      switch (*pf)
+       {
+       case 'h':
+         if (pf[1] == 'h')
+           {
+             ++pf;
+             spec.modifier = FMT_LEN_hh;
+           }
+         else
+           spec.modifier = FMT_LEN_h;
+         ++pf;
+         break;
+
+       case 'j':
+         spec.modifier = FMT_LEN_j;
+         ++pf;
+         break;
+
+       case 'L':
+         spec.modifier = FMT_LEN_L;
+         ++pf;
+         break;
+
+       case 'l':
+         if (pf[1] == 'l')
+           {
+             ++pf;
+             spec.modifier = FMT_LEN_ll;
+           }
+         else
+           spec.modifier = FMT_LEN_l;
+         ++pf;
+         break;
+
+       case 't':
+         spec.modifier = FMT_LEN_t;
+         ++pf;
+         break;
+
+       case 'z':
+         spec.modifier = FMT_LEN_z;
+         ++pf;
+         break;
+       }
+
+      switch (*pf)
+       {
+         /* Handle a sole '%' character the same as "%%" but since it's
+            undefined prevent the result from being folded.  */
+       case '\0':
+         --pf;
+         res->bounded = false;
+       case '%':
+         spec.fmtfunc = format_percent;
+         break;
+
+       case 'a':
+       case 'A':
+       case 'e':
+       case 'E':
+       case 'f':
+       case 'F':
+       case 'g':
+       case 'G':
+         res->floating = true;
+         spec.fmtfunc = format_floating;
+         break;
+
+       case 'd':
+       case 'i':
+       case 'o':
+       case 'u':
+       case 'x':
+       case 'X':
+         spec.fmtfunc = format_integer;
+         break;
+
+       case 'p':
+         spec.fmtfunc = format_pointer;
+         break;
+
+       case 'n':
+         return;
+
+       case 'c':
+       case 'S':
+       case 's':
+         spec.fmtfunc = format_string;
+         break;
+
+       default:
+         return;
+       }
+
+      spec.specifier = *pf++;
+
+      /* Compute the length of the format directive.  */
+      size_t dirlen = pf - dir;
+
+      /* Extract the argument if the directive takes one and if it's
+        available (e.g., the function doesn't take a va_list).  Treat
+        missing arguments the same as va_list, even though they will
+        have likely already been diagnosed by -Wformat.  */
+      tree arg = NULL_TREE;
+      if (spec.specifier != '%'
+         && argno < gimple_call_num_args (info.callstmt))
+       arg = gimple_call_arg (info.callstmt, dollar ? dollar : argno++);
+
+      ::format_directive (info, res, dir, dirlen, spec, arg);
+    }
+}
+
+/* Return the size of the object referenced by the expression DEST if
+   available, or -1 otherwise.  */
+
+static unsigned HOST_WIDE_INT
+get_destination_size (tree dest)
+{
+  /* Use __builtin_object_size to determine the size of the destination
+     object.  When optimizing, determine the smallest object (such as
+     a member array as opposed to the whole enclosing object), otherwise
+     use type-zero object size to determine the size of the enclosing
+     object (the function fails without optimization in this type).  */
+  int ost = 0 < optimize;
+  unsigned HOST_WIDE_INT size;
+  if (compute_builtin_object_size (dest, ost, &size))
+    return size;
+
+  return HOST_WIDE_INT_M1U;
+}
+
+/* Given a suitable result RES of a call to a formatted output function
+   described by INFO, substitute the result for the return value of
+   the call.  The result is suitable if the number of bytes it represents
+   is known and exact.  A result that isn't suitable for substitution may
+   have its range set to the range of return values, if that is known.  */
+
+static void
+try_substitute_return_value (gimple_stmt_iterator gsi,
+                            const pass_sprintf_length::call_info &info,
+                            const format_result &res)
+{
+  tree lhs = gimple_get_lhs (info.callstmt);
+
+  /* Avoid the return value optimization when the behavior of the call
+     is undefined either because any directive may have produced 4K or
+     more of output, or the return value exceeds INT_MAX, or because
+     the output overflows the destination object (but leave it enabled
+     when the function is bounded because then the behavior is well-
+     defined).  */
+  if (lhs && res.bounded && res.under4k
+      && (info.bounded || res.number_chars <= info.objsize)
+      && res.number_chars - 1 <= target_int_max ())
+    {
+      /* Replace the left-hand side of the call with the constant
+        result of the formatted function minus 1 for the terminating
+        NUL which the functions' return value does not include.  */
+      gimple_call_set_lhs (info.callstmt, NULL_TREE);
+      tree cst = build_int_cst (integer_type_node, res.number_chars - 1);
+      gimple *g = gimple_build_assign (lhs, cst);
+      gsi_insert_after (&gsi, g, GSI_NEW_STMT);
+      update_stmt (info.callstmt);
+
+      if (dump_file)
+       {
+         location_t callloc = gimple_location (info.callstmt);
+         fprintf (dump_file, "On line %i substituting ",
+                  LOCATION_LINE (callloc));
+         print_generic_expr (dump_file, cst, dump_flags);
+         fprintf (dump_file, " for ");
+         print_generic_expr (dump_file, info.func, dump_flags);
+         fprintf (dump_file, " return value (output %s).\n",
+                  res.constant ? "constant" : "variable");
+       }
+    }
+  else
+    {
+      unsigned HOST_WIDE_INT maxbytes;
+
+      if (lhs
+         && ((maxbytes = res.number_chars - 1) <= target_int_max ()
+             || (res.number_chars_min - 1 <= target_int_max ()
+                 && (maxbytes = res.number_chars_max - 1) <= target_int_max ()))
+         && (info.bounded || maxbytes < info.objsize))
+       {
+         /* If the result is in a valid range bounded by the size of
+            the destination set it so that it can be used for subsequent
+            optimizations.  */
+         int prec = TYPE_PRECISION (integer_type_node);
+
+         if (res.number_chars < target_int_max () && res.under4k)
+           {
+             wide_int num = wi::shwi (res.number_chars - 1, prec);
+             set_range_info (lhs, VR_RANGE, num, num);
+           }
+         else if (res.number_chars_min < target_int_max ()
+                  && res.number_chars_max < target_int_max ())
+           {
+             wide_int min = wi::shwi (res.under4k ? res.number_chars_min - 1
+                                      : target_int_min (), prec);
+             wide_int max = wi::shwi (res.number_chars_max - 1, prec);
+             set_range_info (lhs, VR_RANGE, min, max);
+           }
+       }
+
+      if (dump_file)
+       {
+         const char *inbounds
+           = (res.number_chars_min <= info.objsize
+              ? (res.number_chars_max <= info.objsize
+                 ? "in" : "potentially out-of")
+              : "out-of");
+
+         location_t callloc = gimple_location (info.callstmt);
+         fprintf (dump_file, "On line %i ", LOCATION_LINE (callloc));
+         print_generic_expr (dump_file, info.func, dump_flags);
+
+         const char *ign = lhs ? "" : " ignored";
+         if (res.number_chars >= HOST_WIDE_INT_MAX)
+           fprintf (dump_file,
+                    " %s-bounds return value in range [%lu, %lu]%s.\n",
+                    inbounds,
+                    (unsigned long)res.number_chars_min,
+                    (unsigned long)res.number_chars_max, ign);
+         else
+           fprintf (dump_file, " %s-bounds return value %lu%s.\n",
+                    inbounds, (unsigned long)res.number_chars, ign);
+       }
+    }
+}
+
+/* Determine if a GIMPLE CALL is to one of the sprintf-like built-in
+   functions and if so, handle it.  */
+
+void
+pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator gsi)
+{
+  call_info info = call_info ();
+
+  info.callstmt = gsi_stmt (gsi);
+  info.func = gimple_call_fn (info.callstmt);
+  if (!info.func)
+    return;
+
+  if (TREE_CODE (info.func) == ADDR_EXPR)
+    info.func = TREE_OPERAND (info.func, 0);
+
+  if (TREE_CODE (info.func) != FUNCTION_DECL
+      || !DECL_BUILT_IN(info.func)
+      || DECL_BUILT_IN_CLASS (info.func) != BUILT_IN_NORMAL)
+    return;
+
+  info.fncode = DECL_FUNCTION_CODE (info.func);
+
+  /* The size of the destination as in snprintf(dest, size, ...).  */
+  unsigned HOST_WIDE_INT dstsize = HOST_WIDE_INT_M1U;
+
+  /* The size of the destination determined by __builtin_object_size.  */
+  unsigned HOST_WIDE_INT objsize = HOST_WIDE_INT_M1U;
+
+  /* Buffer size argument number (snprintf and vsnprintf).  */
+  unsigned HOST_WIDE_INT idx_dstsize = HOST_WIDE_INT_M1U;
+
+  /* Object size argument number (snprintf_chk and vsnprintf_chk).  */
+  unsigned HOST_WIDE_INT idx_objsize = HOST_WIDE_INT_M1U;
+
+  /* Format string argument number (valid for all functions).  */
+  unsigned idx_format;
+
+  switch (info.fncode)
+    {
+    case BUILT_IN_SPRINTF:
+      // Signature:
+      //   __builtin_sprintf (dst, format, ...)
+      idx_format = 1;
+      info.argidx = 2;
+      break;
+
+    case BUILT_IN_SNPRINTF:
+      // Signature:
+      //   __builtin_snprintf (dst, size, format, ...)
+      idx_dstsize = 1;
+      idx_format = 2;
+      info.argidx = 3;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_SNPRINTF_CHK:
+      // Signature:
+      //   __builtin___sprintf_chk (dst, size, ost, objsize, format, ...)
+      idx_dstsize = 1;
+      idx_objsize = 3;
+      idx_format = 4;
+      info.argidx = 5;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_SPRINTF_CHK:
+      // Signature:
+      //   __builtin___sprintf_chk (dst, ost, objsize, format, ...)
+      idx_objsize = 2;
+      idx_format = 3;
+      info.argidx = 4;
+      break;
+
+    case BUILT_IN_VSNPRINTF:
+      // Signature:
+      //   __builtin_vsprintf (dst, size, format, va)
+      idx_dstsize = 1;
+      idx_format = 2;
+      info.argidx = -1;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_VSNPRINTF_CHK:
+      // Signature:
+      //   __builtin___vsnprintf_chk (dst, size, ost, objsize, format, va)
+      idx_dstsize = 1;
+      idx_objsize = 2;
+      idx_format = 3;
+      info.argidx = -1;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_VSPRINTF:
+      // Signature:
+      //   __builtin_vsprintf (dst, format, va)
+      idx_format = 1;
+      info.argidx = -1;
+      break;
+
+    case BUILT_IN_VSPRINTF_CHK:
+      // Signature:
+      //   __builtin___vsprintf_chk (dst, ost, objsize, format, va)
+      idx_format = 3;
+      idx_objsize = 2;
+      info.argidx = -1;
+      break;
+
+    default:
+      return;
+    }
+
+  info.format = gimple_call_arg (info.callstmt, idx_format);
+
+  if (idx_dstsize == HOST_WIDE_INT_M1U)
+    {
+      // For non-bounded functions like sprintf, to to determine
+      // the size of the destination from the object or pointer
+      // passed to it as the first argument.
+      dstsize = get_destination_size (gimple_call_arg (info.callstmt, 0));
+    }
+  else if (tree size = gimple_call_arg (info.callstmt, idx_dstsize))
+    {
+      /* For bounded functions try to get the size argument.  */
+
+      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)
+           warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
+                       "specified destination size %wu too large",
+                       dstsize);
+       }
+      else if (TREE_CODE (size) == SSA_NAME)
+       {
+         /* Try to determine the range of values of the argument
+            and use the greater of the two at -Wformat-level 1 and
+            the smaller of them at level 2.  */
+         wide_int min, max;
+         enum value_range_type range_type
+           = get_range_info (size, &min, &max);
+         if (range_type == VR_RANGE)
+           {
+             dstsize
+               = (warn_format_length < 2
+                  ? wi::fits_uhwi_p (max) ? max.to_uhwi () : max.to_shwi ()
+                  : wi::fits_uhwi_p (min) ? min.to_uhwi () : min.to_shwi ());
+           }
+       }
+    }
+
+  if (idx_objsize != HOST_WIDE_INT_M1U)
+    {
+      if (tree size = gimple_call_arg (info.callstmt, idx_objsize))
+         if (tree_fits_uhwi_p (size))
+           objsize = tree_to_uhwi (size);
+    }
+
+  if (info.bounded && !dstsize)
+    {
+      /* As a special case, when the explicitly specified destination
+        size argument (to a bounded function like snprintf) is zero
+        it is a request to determine the number of bytes on output
+        without actually producing any.  Pretend the size is
+        unlimited in this case.  */
+      info.objsize = HOST_WIDE_INT_MAX;
+    }
+  else
+    {
+      /* Set the object size to the smaller of the two arguments
+        of both have been specified and they're not equal.  */
+      info.objsize = dstsize < objsize ? dstsize : objsize;
+
+      if (info.bounded
+         && dstsize != HOST_WIDE_INT_M1U && objsize < dstsize)
+       {
+         warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
+                     "specified size %wu exceeds the size %wu "
+                     "of the destination object", dstsize, objsize);
+       }
+    }
+
+  if (integer_zerop (info.format))
+    {
+      /* This is diagnosed with -Wformat only when the null is a constant
+        pointer.  The warning here diagnoses instances where the pointer
+        is not constant.  */
+      warning_at (EXPR_LOC_OR_LOC (info.format, input_location),
+                 OPT_Wformat_length_, "null format string");
+      return;
+    }
+
+  info.fmtstr = get_format_string (info.format, &info.fmtloc);
+  if (!info.fmtstr)
+    return;
+
+  /* The result is the number of bytes output by the formatted function,
+     including the terminating NUL.  */
+  format_result res = format_result ();
+  compute_format_length (info, &res);
+
+  /* When optimizing and the printf return value optimization is enabled,
+     attempt to substitute the computed result for the return value of
+     the call.  Avoid this optimization when -frounding-math is in effect
+     and the format string contains a floating point directive.  */
+  if (0 < optimize && flag_printf_return_value
+      && (!flag_rounding_math || !res.floating))
+    try_substitute_return_value (gsi, info, res);
+}
+
+/* Execute the pass for function FUN.  */
+
+unsigned int
+pass_sprintf_length::execute (function *fun)
+{
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si);
+          gsi_next (&si))
+       {
+         /* Iterate over statements, looking for function calls.  */
+         gimple *stmt = gsi_stmt (si);
+
+         if (gimple_code (stmt) == GIMPLE_CALL)
+           handle_gimple_call (si);
+       }
+    }
+
+  return 0;
+}
+
+}   /* Unnamed namespace.  */
+
+/* Return a pointer to a pass object newly constructed from the context
+   CTXT.  */
+
+gimple_opt_pass *
+make_pass_sprintf_length (gcc::context *ctxt)
+{
+  return new pass_sprintf_length (ctxt);
+}
index 6a83371f8c23af3745d9a125ce2c7d8643be2a1b..1375254e971fa70e8a8b4d8e50b6b369f8811a0c 100644 (file)
@@ -43,6 +43,7 @@ along with GCC; see the file COPYING3.  If not see
   NEXT_PASS (pass_warn_function_return);
   NEXT_PASS (pass_expand_omp);
   NEXT_PASS (pass_build_cgraph_edges);
+  NEXT_PASS (pass_sprintf_length, false);
   TERMINATE_PASS_LIST (all_lowering_passes)
 
   /* Interprocedural optimization passes.  */
@@ -306,6 +307,7 @@ along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_simduid_cleanup);
       NEXT_PASS (pass_lower_vector_ssa);
       NEXT_PASS (pass_cse_reciprocals);
+      NEXT_PASS (pass_sprintf_length, true);
       NEXT_PASS (pass_reassoc, false /* insert_powi_p */);
       NEXT_PASS (pass_strength_reduction);
       NEXT_PASS (pass_split_paths);
index e55b6bd6fb6c6151cbdc10ef7972b45b8c794fdf..60957f9525974be74ef8f1b7cdabe50a0718ad2a 100644 (file)
@@ -769,7 +769,8 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
 
        case VECTOR_CST:
          {
-           char buf[10];
+           /* Big enough for 2 UINT_MAX plus the string below.  */
+           char buf[32];
            unsigned i;
 
            for (i = 0; i < VECTOR_CST_NELTS (node); ++i)
index 0e10d6f03c8b488402b7c420f7c1c570152c632c..33acc79d5ed24f24293158bf69dcc2fb46631ad0 100644 (file)
@@ -3353,6 +3353,12 @@ greater than 128 and a multiple of 32.",
  machine_mode, (int n, bool extended),
  default_floatn_mode)
 
+DEFHOOK
+(printf_pointer_format,
+ "Determine the target @code{printf} implementation format string that the most closely corresponds to the @code{%p} format directive.  The object pointed to by the @var{flags} is set to a string consisting of recognized format flags such as the @code{'#'} character.",
+ const char*, (tree, const char **flags),
+ default_printf_pointer_format)
+
 /* Compute cost of moving data from a register of class FROM to one of
    TO, using MODE.  */
 DEFHOOK
index 67cc53a2641c2b472e45e21d7d1f2cb5682a2f90..d75650fede333ecdc3d727ba107a0447ef9a1923 100644 (file)
@@ -1509,6 +1509,20 @@ no_c99_libc_has_function (enum function_class fn_class ATTRIBUTE_UNUSED)
   return false;
 }
 
+/* Return the format string to which "%p" corresponds.  By default,
+   assume it corresponds to the C99 "%zx" format and set *FLAGS to
+   the empty string to indicate that format flags have no effect.
+   An example of an implementation that matches this description
+   is AIX.  */
+
+const char*
+default_printf_pointer_format (tree, const char **flags)
+{
+  *flags = "";
+
+  return "%zx";
+}
+
 tree
 default_builtin_tm_load_store (tree ARG_UNUSED (type))
 {
index a2fa49f89b35127cc09c19bff84e15415d989b27..3356f0afe0fbb523c7917969f5f84a48fd4d97ab 100644 (file)
@@ -191,6 +191,10 @@ extern bool default_libc_has_function (enum function_class);
 extern bool no_c99_libc_has_function (enum function_class);
 extern bool gnu_libc_has_function (enum function_class);
 
+extern const char* default_printf_pointer_format (tree, const char **);
+extern const char* gnu_libc_printf_pointer_format (tree, const char **);
+extern const char* solaris_printf_pointer_format (tree, const char **);
+
 extern tree default_builtin_tm_load_store (tree);
 
 extern int default_memory_move_cost (machine_mode, reg_class_t, bool);
index 006607b19ab6e33516112ec90716aa7742a7b0a3..6d26e43fcef92e79197815eeb57c52dc3ea1683e 100644 (file)
@@ -1,3 +1,14 @@
+2016-09-20  Martin Sebor  <msebor@redhat.com>
+
+       PR middle-end/49905
+       * gcc.dg/builtin-stringop-chk-1.c: Adjust.
+       * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: New test.
+       * gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: New test.
+       * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: New test.
+       * gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: New test.
+       * gcc.dg/tree-ssa/builtin-sprintf.c: New test.
+       * gcc.dg/tree-ssa/builtin-sprintf-2.c: New test.
+
 2016-09-21  Kugan Vivekanandarajah  <kuganv@linaro.org>
 
        * gcc.dg/guality/pr54519-1.c: Add -fno-ipa-vrp. Else constant
index 6e71aeeec28d1ed04b10c88fddd9afa709cfd78e..e491ff52680171d70e57046094d895dfecd4a288 100644 (file)
@@ -1,7 +1,7 @@
 /* Test whether buffer overflow warnings for __*_chk builtins
    are emitted properly.  */
 /* { dg-do compile } */
-/* { dg-options "-O2 -std=gnu99 -ftrack-macro-expansion=0" } */
+/* { dg-options "-O2 -Wno-format -std=gnu99 -ftrack-macro-expansion=0" } */
 /* { dg-additional-options "-mstructure-size-boundary=8" { target arm*-*-* } } */
 // { dg-skip-if "packed attribute missing for t" { "epiphany-*-*" } { "*" } { "" } }
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
new file mode 100644 (file)
index 0000000..f7abfd8
--- /dev/null
@@ -0,0 +1,218 @@
+/* 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
+   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
+   the removal of code.
+   The test is compiled with warnings disabled to make sure the absence
+   of optimizations does not depend on the presence of warnings.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fprintf-return-value -fdump-tree-optimized -ftrack-macro-expansion=0 -w" } */
+
+#ifndef LINE
+# define LINE 0
+#endif
+
+#define INT_MAX __INT_MAX__
+
+char *buf;
+char buf8k [8192];
+
+#define concat(a, b)   a ## b
+#define CAT(a, b)      concat (a, b)
+
+#define EQL(expect, size, fmt, ...)                                    \
+  void CAT (test_on_line_, __LINE__)(void)                             \
+  {                                                                    \
+    if (!LINE || LINE == __LINE__)                                     \
+      {                                                                        \
+       char *dst = size < 0 ? buf : buf8k + sizeof buf8k - size;       \
+       int result = __builtin_sprintf (dst, fmt, __VA_ARGS__);         \
+       if (result != expect)                                           \
+         __builtin_abort ();                                           \
+      }                                                                        \
+  }
+
+/* Verify that the return value or range or return values from the call
+   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)                             \
+  {                                                                    \
+    if (!LINE || LINE == __LINE__)                                     \
+      {                                                                        \
+       char *dst = size < 0 ? buf : buf8k + sizeof buf8k - size;       \
+       int result = __builtin_sprintf (dst, fmt, __VA_ARGS__);         \
+       if (result < min || max < result)                               \
+         __builtin_abort ();                                           \
+      }                                                                        \
+  }
+
+extern int i;
+extern long li;
+extern char *str;
+
+/* Verify that overflowing the destination object disables the return
+   value optimization.  */
+EQL (0, 0, "%c",  ' ');
+EQL (0, 0, "%c",  i)
+EQL (0, 0, "%-s", "");
+
+EQL (1, 1, "%c",  'x');
+EQL (1, 1, "%-s", "x");
+
+EQL (4, 4, "%4c", 'x');
+
+/* Verify that exceeding the environmental limit of 4095 bytes for
+   a single conversion specification disables the return value
+   folding.  */
+EQL (   4096, sizeof buf8k, "%4096c", 'x');
+
+EQL (INT_MAX, -1, "%*c", INT_MAX, 'x');
+
+EQL (   4096, sizeof buf8k, "%4096.4094f", 1.0);
+EQL (   4096, sizeof buf8k, "%.4094f",     1.0);
+EQL (   4097, sizeof buf8k, "%.4095f",     1.0);
+
+enum { imax2 = (INT_MAX / 2) * 2 };
+EQL (imax2, -1, "%*c%*c", INT_MAX / 2, 'x', INT_MAX / 2, 'y');
+
+/* Verify that range inforation for calls that overflow the destination
+   isn't available.  */
+RNG (0,  0,  0, "%hhi", i)
+RNG (0,  0,  1, "%hhi", i)
+RNG (0,  1,  1, "%hhi", i)
+RNG (0,  0,  2, "%hhi", i)
+RNG (0,  1,  2, "%hhi", i)
+RNG (0,  2,  2, "%hhi", i)
+RNG (0,  0,  3, "%hhi", i)
+RNG (0,  1,  3, "%hhi", i)
+RNG (0,  2,  3, "%hhi", i)
+RNG (0,  3,  3, "%hhi", i)
+RNG (0,  0,  4, "%hhi", i)
+RNG (0,  1,  4, "%hhi", i)
+RNG (0,  2,  4, "%hhi", i)
+RNG (0,  3,  4, "%hhi", i)
+RNG (0,  4,  4, "%hhi", i)
+
+RNG (0,  0,  0, "%hhu", i)
+RNG (0,  0,  1, "%hhu", i)
+RNG (0,  1,  1, "%hhu", i)
+RNG (0,  0,  2, "%hhu", i)
+RNG (0,  1,  2, "%hhu", i)
+RNG (0,  2,  2, "%hhu", i)
+RNG (0,  0,  3, "%hhu", i)
+RNG (0,  1,  3, "%hhu", i)
+RNG (0,  2,  3, "%hhu", i)
+RNG (0,  3,  3, "%hhu", i)
+
+RNG (0,  0,  0, "%i", i)
+
+RNG (0,  0,  1, "%i", i)
+RNG (0,  1,  1, "%i", i)
+
+RNG (0,  0,  2, "%i", i)
+RNG (0,  1,  2, "%i", i)
+RNG (0,  2,  2, "%i", i)
+
+RNG (0,  0,  3, "%i", i)
+RNG (0,  1,  3, "%i", i)
+RNG (0,  2,  3, "%i", i)
+RNG (0,  3,  3, "%i", i)
+
+RNG (0,  0,  4, "%i", i)
+RNG (0,  1,  4, "%i", i)
+RNG (0,  2,  4, "%i", i)
+RNG (0,  3,  4, "%i", i)
+RNG (0,  4,  4, "%i", i)
+
+RNG (0,  0,  5, "%i", i)
+RNG (0,  1,  5, "%i", i)
+RNG (0,  2,  5, "%i", i)
+RNG (0,  3,  5, "%i", i)
+RNG (0,  4,  5, "%i", i)
+RNG (0,  5,  5, "%i", i)
+
+RNG (0,  0,  6, "%i", i)
+RNG (0,  1,  6, "%i", i)
+RNG (0,  2,  6, "%i", i)
+RNG (0,  3,  6, "%i", i)
+RNG (0,  4,  6, "%i", i)
+RNG (0,  5,  6, "%i", i)
+RNG (0,  6,  6, "%i", i)
+
+RNG (0,  0,  7, "%i", i)
+RNG (0,  1,  7, "%i", i)
+RNG (0,  2,  7, "%i", i)
+RNG (0,  3,  7, "%i", i)
+RNG (0,  4,  7, "%i", i)
+RNG (0,  5,  7, "%i", i)
+RNG (0,  6,  7, "%i", i)
+
+#if __SIZEOF_INT__ == 4
+
+/* A 32-bit int takes up at most 11 bytes (-2147483648) not including
+   the terminating nul.  */
+RNG (0,  7,  7, "%i", i)
+
+RNG (0,  0,  8, "%i", i)
+RNG (0,  1,  8, "%i", i)
+RNG (0,  2,  8, "%i", i)
+RNG (0,  3,  8, "%i", i)
+RNG (0,  4,  8, "%i", i)
+RNG (0,  5,  8, "%i", i)
+RNG (0,  6,  8, "%i", i)
+RNG (0,  7,  8, "%i", i)
+RNG (0,  8,  8, "%i", i)
+
+RNG (0,  0,  9, "%i", i)
+RNG (0,  1,  9, "%i", i)
+RNG (0,  2,  9, "%i", i)
+RNG (0,  3,  9, "%i", i)
+RNG (0,  4,  9, "%i", i)
+RNG (0,  5,  9, "%i", i)
+RNG (0,  6,  9, "%i", i)
+RNG (0,  7,  9, "%i", i)
+RNG (0,  8,  9, "%i", i)
+RNG (0,  9,  9, "%i", i)
+
+RNG (0,  0, 10, "%i", i)
+RNG (0,  1, 10, "%i", i)
+RNG (0,  2, 10, "%i", i)
+RNG (0,  3, 10, "%i", i)
+RNG (0,  4, 10, "%i", i)
+RNG (0,  5, 10, "%i", i)
+RNG (0,  6, 10, "%i", i)
+RNG (0,  7, 10, "%i", i)
+RNG (0,  8, 10, "%i", i)
+RNG (0,  9, 10, "%i", i)
+RNG (0, 10, 10, "%i", i)
+
+#endif
+
+/* 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);
+
+/* 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
+   abort has not been eliminated either.
+
+   The expected output looks something like this:
+
+   <bb 2>:
+   result_3 = __builtin_sprintf (&MEM[(void *)&buf8k + 8192B], "%c", 32);
+   if (result_3 != 0)
+     goto <bb 3>;
+   else
+     goto <bb 4>;
+
+   <bb 3>:
+   __builtin_abort ();
+
+*/
+
+/* { 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 } } } } } */
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
new file mode 100644 (file)
index 0000000..7261dbd
--- /dev/null
@@ -0,0 +1,1417 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+/* When debugging, define LINE to the line number of the test case to exercise
+   and avoid exercising any of the others.  The buffer and objsize macros
+   below make use of LINE to avoid warnings for other lines.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+#define INT_MAX __INT_MAX__
+
+char buffer [256];
+extern char *ptr;
+
+/* Evaluate to an array of SIZE characters when non-negative and LINE
+   is not set or set to the line the macro is on, or to a pointer to
+   an unknown object otherwise.  */
+#define buffer(size)                                                   \
+  (0 <= size && (!LINE || __LINE__ == LINE)                            \
+   ? buffer + sizeof buffer - size : ptr)
+
+/* Evaluate to SIZE when non-negative and LINE is not set or set to
+   the line the macro is on, or to SIZE_MAX otherise.  */
+#define objsize(size)                                                  \
+  (0 <= size && (!LINE || __LINE__ == LINE)                            \
+   ? size : __SIZE_MAX__)
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+const char s0[] = "";
+const char s1[] = "1";
+const char s2[] = "12";
+const char s3[] = "123";
+const char s4[] = "1234";
+const char s5[] = "12345";
+const char s6[] = "123456";
+const char s7[] = "1234567";
+const char s8[] = "12345678";
+
+void sink (void*, ...);
+
+/* Macro to verify that calls to __builtin_sprintf (i.e., with no size
+   argument) issue diagnostics by correctly determining the size of
+   the destination buffer.  */
+#define T(size, fmt, ...)                                              \
+  __builtin_sprintf (buffer (size), fmt, __VA_ARGS__),                 \
+    sink (buffer, ptr);
+
+/* Exercise the "%c" and "%lc" directive with constant arguments.  */
+
+void test_sprintf_c_const (void)
+{
+  T (-1, "%c",    0);           /* No warning for unknown destination size.  */
+  T ( 0, "%c",    0);           /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%c",    0);           /* { dg-warning "writing a terminating nul past the end" } */
+  T ( 1, "%c",   '1');          /* { dg-warning "nul past the end" } */
+  T ( 2, "%c",   '1');
+  T ( 2, "%2c",  '1');          /* { dg-warning "nul past the end" } */
+  T ( 2, "%3c",  '1');          /* { dg-warning "into a region" } */
+  T ( 2, "%c%c", '1', '2');     /* { dg-warning "nul past the end" } */
+  T ( 3, "%c%c", '1', '2');
+
+  T ( 2, "%1$c%2$c", '1', '2'); /* { dg-warning "does not support %n.|nul past the end" } */
+  T ( 3, "%1$c%2$c", '1', '2');
+
+  /* Verify that a warning is issued for exceeding INT_MAX bytes and
+     not otherwise.  */
+  T (-1, "%*c",  INT_MAX - 1, '1');
+  T (-1, "%*c",  INT_MAX,     '1');
+  T (-1, "X%*c", INT_MAX - 1, '1');
+  T (-1, "X%*c", INT_MAX,     '1'); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T (-1, "%*c%*c", INT_MAX - 1, '1', INT_MAX - 1, '2'); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T (-1, "%*cX", INT_MAX - 2, '1');
+  T (-1, "%*cX", INT_MAX - 1, '1');
+  T (-1, "%*cX", INT_MAX,     '1'); /* { dg-warning "output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+}
+
+/* Exercise the "%p" directive with constant arguments.  */
+
+void test_sprintf_p_const (void)
+{
+  /* GLIBC and uClibc format null pointers as "(nil)".  Sane implementations
+     format null pointers as 0 or 0x0 and so the following will only be
+     diagnosed on the former targets.  */
+  T (5, "%p",     (void*)0);
+  /* { dg-warning "nul past the end" "(nil)" { target *-linux-gnu *-*-uclinux } 94 } */
+
+  /* The exact output for %p is unspecified by C.  Two formats are known:
+     same as %tx (for example AIX) and same as %#tx (for example Solaris).  */
+  T (0, "%p",     (void*)0x1);    /* { dg-warning ".%p. directive writing . bytes into a region of size 0" } */
+  T (1, "%p",     (void*)0x12);   /* { dg-warning ".%p. directive writing . bytes into a region of size 1" } */
+  T (2, "%p",     (void*)0x123);  /* { dg-warning ".%p. directive writing . bytes into a region of size 2" } */
+
+  /* GLIBC and uClibc treat the ' ' flag with the "%p" directive the same
+     as with signed integer conversions (i.e., it prepends a space).  Other
+     known implementations ignore it.  */
+  T (6, "% p",    (void*)0x234);  /* { dg-warning ". . flag used with .%p." } */
+  /* { dg-warning "nul past the end" "Glibc %p" { target *-linux-gnu } 106 } */
+  /* { dg-warning "nul past the end" "Generic %p" { target *-*-uclinux } 106 } */
+}
+
+/* Verify that no warning is issued for calls that write into a flexible
+   array member whose size isn't known.  Also verify that calls that use
+   a flexible array member as an argument to the "%s" directive do not
+   cause a warning.  */
+
+void test_sprintf_flexarray (void *p, int i)
+{
+  struct S
+  {
+    int n;
+    char a [];
+  } *s = p;
+
+  __builtin_sprintf (s->a, "%c",       'x');
+
+  __builtin_sprintf (s->a, "%s",       "");
+  __builtin_sprintf (s->a, "%s",       "abc");
+  __builtin_sprintf (s->a, "abc%sghi", "def");
+
+  __builtin_sprintf (s->a, "%i",       1234);
+
+  __builtin_sprintf (buffer (1), "%s",  s->a);
+  __builtin_sprintf (buffer (1), "%s",  s [i].a);
+}
+
+/* Same as above but for zero-length arrays.  */
+
+void test_sprintf_zero_length_array (void *p, int i)
+{
+  struct S
+  {
+    int n;
+    char a [0];
+  } *s = p;
+
+  __builtin_sprintf (s->a, "%c",       'x');
+
+  __builtin_sprintf (s->a, "%s",       "");
+  __builtin_sprintf (s->a, "%s",       "abc");
+  __builtin_sprintf (s->a, "abc%sghi", "def");
+
+  __builtin_sprintf (s->a, "%i",       1234);
+
+  __builtin_sprintf (buffer (1), "%s",  s->a);
+  __builtin_sprintf (buffer (1), "%s",  s [i].a);
+}
+
+/* Verify that the note printed along with the diagnostic mentions
+   the correct sizes and refers to the location corresponding to
+   the affected directive.  */
+
+void test_sprintf_note (void)
+{
+#define P __builtin_sprintf
+
+  /* Diagnostic column numbers are 1-based.  */
+
+  P (buffer (0),                /* { dg-message "format output 4 bytes into a destination of size 0" } */
+     "%c%s%i", '1', "2", 3);    /* { dg-warning "7:.%c. directive writing 1 byte into a region of size 0" } */
+
+  P (buffer (1),                /* { dg-message "format output 6 bytes into a destination of size 1" } */
+     "%c%s%i", '1', "23", 45);  /* { dg-warning "9:.%s. directive writing 2 bytes into a region of size 0" } */
+
+  P (buffer (2),                /* { dg-message "format output 6 bytes into a destination of size 2" } */
+     "%c%s%i", '1', "2", 345);  /* { dg-warning "11:.%i. directive writing 3 bytes into a region of size 0" } */
+
+  /* It would be nice if the caret in the location range for the format
+     string below could be made to point at the closing quote of the format
+     string, like so:
+       sprintf (d, "%c%s%i", '1', "2", 3456);
+                   ~~~~~~^
+     Unfortunately, that doesn't work with the current setup.  */
+  P (buffer (6),                /* { dg-message "format output 7 bytes into a destination of size 6" } */
+     "%c%s%i", '1', "2", 3456); /* { dg-warning "writing a terminating nul past the end of the destination" } */
+}
+
+#undef T
+#define T(size, fmt, ...)                                        \
+  __builtin___sprintf_chk (buffer (size), 0, objsize (size), fmt, \
+                          __VA_ARGS__), sink (buffer, ptr)
+
+/* Exercise the "%c" and "%lc" directive with constant arguments.  */
+
+void test_sprintf_chk_c_const (void)
+{
+  T (-1, "%c",    0);            /* No warning for unknown destination size.  */
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+  T (0, "%c",     0);            /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+  T (1, "%c",     0);            /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%c",   '1');            /* { dg-warning "nul past the end" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "nul past the end" } */
+  T (2, "%3c",  '1');            /* { dg-warning "into a region" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "nul past the end" } */
+  T (3, "%c%c", '1', '2');
+
+  /* Wide characters.  */
+  T (0, "%lc",     0);           /* { dg-warning "nul past the end" } */
+  T (1, "%lc",     0);
+  T (1, "%lc%lc",  0, 0);
+  T (2, "%lc",     0);
+  T (2, "%lc%lc",  0, 0);
+
+  /* The following could result in as few as no bytes and in as many as
+     MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing some unknown number of bytes into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "nul past the end" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* Here in the best case each argument will format as single character,
+     causing the terminating NUL to be written past the end.  */
+  T (3, "%lc%c%c", L'1', '2', '3');   /* { dg-warning "nul past the end" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%s" and "%ls" directive with constant arguments.  */
+
+void test_sprintf_chk_s_const (void)
+{
+  T (-1, "%*s",  0, "");        /* No warning for unknown destination size.  */
+  T ( 0, "%*s",  0, "");        /* { dg-warning "nul past the end" } */
+  T ( 0, "%-s",     "");        /* { dg-warning "nul past the end" } */
+  T ( 0, "%*s",  0, s0);        /* { dg-warning "nul past the end" } */
+  T ( 1, "%*s",  0, "");
+  T ( 1, "%*s",  0, s0);
+  T ( 1, "%*s",  0, "\0");
+  T ( 1, "%*s",  0, "1");       /* { dg-warning "nul past the end" } */
+  T ( 1, "%*s",  0, s1);        /* { dg-warning "nul past the end" } */
+  T ( 1, "%1s",     "");        /* { dg-warning "nul past the end" } */
+  T ( 1, "%1s",     s0);        /* { dg-warning "nul past the end" } */
+  T (-1, "%1s",    "1");        /* No warning for unknown destination size.  */
+  T ( 1, "%*s",  1, "");        /* { dg-warning "nul past the end" } */
+  T ( 1, "%*s",  1, s0);        /* { dg-warning "nul past the end" } */
+  T (-1, "%*s",  1, s0);        /* No warning for unknown destination size.  */
+
+  T (1, "%.0s",    "123");
+  T (1, "%.0s",    s3);
+  T (1, "%.*s", 0, "123");
+  T (1, "%.*s", 0, s3);
+  T (1, "%.1s",    "123");      /* { dg-warning "nul past the end" } */
+  T (1, "%.1s",    s3);         /* { dg-warning "nul past the end" } */
+  T (1, "%.*s", 1, "123");      /* { dg-warning "nul past the end" } */
+  T (1, "%.*s", 1, s3);         /* { dg-warning "nul past the end" } */
+
+  T (2, "%.*s", 0, "");
+  T (2, "%.*s", 0, "1");
+  T (2, "%.*s", 0, s1);
+  T (2, "%.*s", 0, "1\0");
+  T (2, "%.*s", 0, "12");
+  T (2, "%.*s", 0, s2);
+
+  T (2, "%.*s", 1, "");
+  T (2, "%.*s", 1, "1");
+  T (2, "%.*s", 1, s1);
+  T (2, "%.*s", 1, "1\0");
+  T (2, "%.*s", 1, "12");
+  T (2, "%.*s", 1, s2);
+
+  T (2, "%.*s", 2, "");
+  T (2, "%.*s", 2, "1");
+  T (2, "%.*s", 2, s1);
+  T (2, "%.*s", 2, "1\0");
+  T (2, "%.*s", 2, "12");       /* { dg-warning "nul past the end" } */
+  T (2, "%.*s", 2, s2);         /* { dg-warning "nul past the end" } */
+
+  T (2, "%.*s", 3, "");
+  T (2, "%.*s", 3, "1");
+  T (2, "%.*s", 3, s1);
+  T (2, "%.*s", 3, "1\0");
+  T (2, "%.*s", 3, "12");       /* { dg-warning "nul past the end" } */
+  T (2, "%.*s", 3, "123");      /* { dg-warning "into a region" } */
+  T (2, "%.*s", 3, s2);         /* { dg-warning "nul past the end" } */
+  T (2, "%.*s", 3, s3);         /* { dg-warning "into a region" } */
+
+  T (2, "%*s",  0, "");
+  T (2, "%*s",  0, "1");
+  T (2, "%*s",  0, s1);
+  T (2, "%*s",  0, "1\0");
+  T (2, "%*s",  0, "12");       /* { dg-warning "nul past the end" } */
+  T (2, "%*s",  0, s2);         /* { dg-warning "nul past the end" } */
+
+  /* Verify that output in excess of INT_MAX bytes is diagnosed even
+     when the size of the destination object is unknown.  */
+  T (-1, "%*s",  INT_MAX - 1, "");
+  T (-1, "%*s",  INT_MAX,     "");
+  T (-1, "X%*s", INT_MAX,     ""); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  /* Multiple directives.  */
+
+  T (1, "%s%s", "", "");
+  T (1, "%s%s", s0, s0);
+  T (1, "%s%s", "", "1");       /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", s0, s1);        /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", "1", "");       /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", s1, s0);        /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", "1", "2");      /* { dg-warning "into a region" } */
+  T (1, "%s%s", s1, s1);        /* { dg-warning "into a region" } */
+
+  T (2, "%s%s", "", "");
+  T (2, "%s%s", "", "1");
+  T (2, "%s%s", "1", "");
+  T (2, "%s%s", "", "12");      /* { dg-warning "nul past the end" } */
+  T (2, "%s%s", "1", "2");      /* { dg-warning "nul past the end" } */
+  T (2, "%s%s", "12", "2");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "1", "23");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "12", "3");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "12", "34");    /* { dg-warning "into a region" } */
+
+  T (2, "_%s",   "");
+  T (2, "%%%s",  "");
+  T (2, "%s%%",  "");
+  T (2, "_%s",   "1");          /* { dg-warning "nul past the end" } */
+  T (2, "%%%s",  "1");          /* { dg-warning "nul past the end" } */
+  T (2, "%s%%",  "1");          /* { dg-warning "nul past the end" } */
+  T (2, "_%s",   "12");         /* { dg-warning "into a region" } */
+  T (2, "__%s",  "1");          /* { dg-warning "into a region" } */
+
+  T (2, "%1$s%2$s", "12", "3"); /* { dg-warning ".%2.s. directive writing 1 byte into a region of size 0" } */
+  T (2, "%1$s%1$s", "12");      /* { dg-warning "does not support|.%1.s. directive writing 2 bytes into a region of size 0" } */
+  T (2, "%2$s%1$s", "1", "23"); /* { dg-warning ".%1.s. directive writing 1 byte into a region of size 0" } */
+  T (2, "%2$s%2$s", "1", "23"); /* { dg-warning "unused|%2.s. directive writing 2 bytes into a region of size 0" } */
+
+  T (3, "__%s", "");
+  T (3, "__%s", "1");           /* { dg-warning "nul past the end" } */
+  T (3, "%s_%s", "", "");
+  T (3, "%s_%s", "1", "");
+  T (3, "%s_%s", "", "1");
+  T (3, "%s_%s", "1", "2");     /* { dg-warning "nul past the end" } */
+
+  /* Wide strings.  */
+  T (-1, "%ls",      L"");
+  T ( 0, "%ls",      L"");      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ls",      L"");
+  T ( 1, "%ls",      L"\0");
+  T ( 1, "%1ls",     L"");      /* { dg-warning "nul past the end" } */
+
+  T (0, "%*ls",  0, L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%*ls",  0, L"");
+  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, "%.0ls",    L"1");
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+  T (2, "%.*ls", 1, L"1");
+
+  /* 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
+     character), and at most 2 bytes because of the precision.  Since its
+     output is explicitly bounded it is diagnosed.  */
+  T (2, "%.2ls",    L"1");      /* { dg-warning "nul past the end" } */
+  T (2, "%.*ls", 2, L"1");      /* { dg-warning "nul past the end" } */
+
+  T (3, "%.0ls",    L"1");
+  T (3, "%.1ls",    L"1");
+  T (3, "%.2ls",    L"1");
+}
+
+/* Exercise the "%hhd", "%hhi", "%hho", "%hhu", and "%hhx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_hh_const (void)
+{
+  T (-1, "%hhd",        0);
+
+  T (1, "%hhd",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhd",         1);     /* { 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" } */
+  T (1, "%-hhd",        0);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%hhi",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhi",         1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhi",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        0);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        1);     /* { dg-warning "into a region" } */
+  T (1, "%-hhi",        0);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hhi",         0);
+  T (2, "%hhi",         1);
+  T (2, "%hhi",         9);
+  T (2, "% hhi",        9);     /* { dg-warning "nul past the end" } */
+  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",        -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" } */
+  T (2, "%-hhi",       -1);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hho",         0);
+  T (2, "%hho",         1);
+  T (2, "%hho",         7);
+  T (2, "%hho",       010);     /* { dg-warning "nul past the end" } */
+  T (2, "%hho",       077);     /* { dg-warning "nul past the end" } */
+  T (2, "%hho",        -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hhx",         0);
+  T (2, "%hhX",         1);
+  T (2, "%hhx",         7);
+  T (2, "%hhX",         8);
+  T (2, "%hhx",        -1);     /* { dg-warning "nul past the end" } */
+  T (2, "%hhX",       0xf);
+  T (2, "%hhx",      0x10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hhX",      0xff);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%#hhx",        0);     /* { dg-warning "nul past the end" } */
+  T (2, "%#hhx",        0);
+  T (3, "%#hhx",        1);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%hhd",       255);
+  T (4, "%hhd",       256);
+  T (4, "%hhd",     0xfff);
+  T (4, "%hhd",    0xffff);
+
+  T (4, "%hhi",       255);
+  T (4, "%hhi",       256);
+  T (4, "%hhi",     0xfff);
+  T (4, "%hhi",    0xffff);
+
+  T (4, "%hhu",        -1);
+  T (4, "%hhu",       255);
+  T (4, "%hhu",       256);
+  T (4, "%hhu",     0xfff);
+  T (4, "%hhu",    0xffff);
+
+  T (4, "%#hhx",        0);
+  T (4, "%#hhx",        1);
+  T (4, "%#hhx",       -1);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hhx",      0xf);
+  T (4, "%#hhx",     0x10);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hhx",     0xff);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hhx",    0xfff);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%hhi %hhi",  0,  0);
+  T (4, "%hhi %hhi",  9,  9);
+  T (4, "%hhi %hhi",  1, 10);   /* { dg-warning "nul past the end" } */
+  T (4, "%hhi %hhi", 10,  1);   /* { dg-warning "nul past the end" } */
+  T (4, "%hhi %hhi", 11, 12);   /* { dg-warning "into a region" } */
+
+  T (5, "%0*hhd %0*hhi", 0,  7, 0,   9);
+  T (5, "%0*hhd %0*hhi", 1,  7, 1,   9);
+  T (5, "%0*hhd %0*hhi", 1,  7, 2,   9);
+  T (5, "%0*hhd %0*hhi", 2,  7, 1,   9);
+  T (5, "%0*hhd %0*hhi", 2,  7, 2,   9); /* { dg-warning "nul past the end" } */
+  T (5, "%0*hhd %0*hhi", 0, 12, 0, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+  T (5, "%0*hhd %0*hhi", 1, 12, 1, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+  T (5, "%0*hhd %0*hhi", 2, 12, 3, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+
+  /* FIXME: Move the boundary test cases into a file of their own that's
+     exercised only on targets with the matching type limits (otherwise
+     they'll fail).  */
+#undef MAX
+#define MAX   127
+
+#undef MIN
+#define MIN   (-MAX -1)
+
+  T (1, "%hhi",        MAX);    /* { dg-warning "into a region" } */
+  T (1, "%hhi",        MIN);    /* { dg-warning "into a region" } */
+  T (1, "%hhi",  MAX +   1);    /* { dg-warning "into a region" } */
+
+  T (2, "%hhi",  MAX +   1);    /* { dg-warning "into a region" } */
+  T (2, "%hhi",  MAX +  10);    /* { dg-warning "into a region" } */
+  T (2, "%hhi",  MAX + 100);    /* { dg-warning "into a region" } */
+}
+
+/* Exercise the "%hhd", "%hi", "%ho", "%hu", and "%hx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_h_const (void)
+{
+  T (1, "%hu",          0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",          1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hi",          0);
+  T (2, "%hi",          1);
+  T (2, "%hi",          9);
+  T (2, "% hi",         9);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hi",         9);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hi",         9);
+  T (2, "%hi",         10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hi",         -1);     /* { dg-warning "nul past the end" } */
+  T (2, "% hi",        -2);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hi",        -3);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hi",        -4);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hu",          0);
+  T (2, "%hu",          1);
+  T (2, "%hu",          9);
+  T (2, "%hu",         10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hu",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%ho",          0);
+  T (2, "%ho",          1);
+  T (2, "%ho",          7);
+  T (2, "%ho",        010);     /* { dg-warning "nul past the end" } */
+  T (2, "%ho",        077);     /* { dg-warning "nul past the end" } */
+  T (2, "%ho",       0100);     /* { dg-warning "into a region" } */
+  T (2, "%ho",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hx",          0);
+  T (2, "%hx",          1);
+  T (2, "%hx",          7);
+  T (2, "%hx",        0xf);
+  T (2, "%hx",       0x10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hx",       0xff);     /* { dg-warning "nul past the end" } */
+  T (2, "%hx",      0x100);     /* { dg-warning "into a region" } */
+  T (2, "%hx",         -1);     /* { dg-warning "into a region" } */
+
+  T (3, "% hi",         7);
+  T (3, "%+hi",         8);
+  T (3, "%-hi",         9);
+  T (3, "%hi",         10);
+  T (3, "%hi",         -1);
+  T (3, "% hi",        -2);
+  T (3, "%+hi",        -3);
+  T (3, "%-hi",        -4);
+
+  T (5, "%hu",       9999);
+  T (5, "%hu",      10000);     /* { dg-warning "nul past the end" } */
+  T (5, "%hu",      65535);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%#hx",         0);     /* { dg-warning "nul past the end" } */
+  T (2, "%#hx",         0);
+  T (3, "%#hx",         1);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%#hx",         0);
+  T (4, "%#hx",         1);
+  T (4, "%#hx",       0xf);
+  T (4, "%#hx",      0x10);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hx",      0xff);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hx",     0x100);     /* { dg-warning "into a region" } */
+  T (4, "%#hx",        -1);     /* { dg-warning "into a region" } */
+
+#undef MAX
+#define MAX   65535
+
+  T (1, "%hhu",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhu",         1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhu",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%hhu",       MAX);     /* { dg-warning "into a region" } */
+  T (1, "%hhu",  MAX +  1);     /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with
+   constant arguments.  */
+
+void test_sprintf_chk_integer_const (void)
+{
+  T ( 1, "%i",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",          1);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",         -1);         /* { dg-warning "into a region" } */
+  T ( 1, "%i_",         1);         /* { dg-warning "character ._. at offset 2 past the end" } */
+  T ( 1, "_%i",         1);         /* { dg-warning "into a region" } */
+  T ( 1, "_%i_",        1);         /* { dg-warning "into a region" } */
+  T ( 1, "%o",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%u",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x",         0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",          1);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x",         1);         /* { dg-warning "into a region" } */
+
+  T ( 2, "%i",          0);
+  T ( 2, "%i",          1);
+  T ( 2, "%i",          9);
+  T ( 2, "%i",         -1);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",         10);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i_",         0);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i",         0);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i_",        0);         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 2, "%o",          1);
+  T ( 2, "%o",          7);
+  T ( 2, "%o",        010);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%o",       0100);         /* { dg-warning "into a region" } */
+  T ( 2, "%x",          1);
+  T ( 2, "%#x",         1);         /* { dg-warning "into a region" } */
+  T ( 2, "%x",        0xa);
+  T ( 2, "%x",        0xf);
+  T ( 2, "%x",       0x10);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",       0xff);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",      0x1ff);         /* { dg-warning "into a region" } */
+
+  T ( 3, "%i",          0);
+  T ( 3, "%i",          1);
+  T ( 3, "%i",          9);
+  T ( 3, "%i",         -9);
+  T ( 3, "%i",         10);
+  T ( 3, "%i",         99);
+  T ( 3, "%i",        -99);         /* { dg-warning "nul past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+i",       ~0U);
+  T ( 3, "%-i",       ~0U);
+  T ( 3, "% i",       ~0U);
+
+  T ( 8, "%8u",         1);        /* { dg-warning "nul past the end" } */
+  T ( 9, "%8u",         1);
+
+  T ( 7, "%1$i%2$i%3$i",     1, 23, 456);
+  T ( 8, "%1$i%2$i%3$i%1$i", 1, 23, 456);
+  T ( 8, "%1$i%2$i%3$i%2$i", 1, 23, 456);   /* { dg-warning "nul past the end" } */
+  T ( 8, "%1$i%2$i%3$i%3$i", 1, 23, 456);   /* { dg-warning "into a region" } */
+
+#undef MAX
+#define MAX   2147483647   /* 10 digits.  */
+#undef MIN
+#define MIN   (-MAX -1)    /* Sign plus 10 digits.  */
+
+  T ( 1, "%i",        MAX);         /* { dg-warning "into a region" } */
+  T ( 1, "%i",        MIN);         /* { dg-warning "into a region" } */
+  T ( 2, "%i",        MAX);         /* { dg-warning "into a region" } */
+  T ( 2, "%i",        MIN);         /* { dg-warning "into a region" } */
+  T (10, "%i",  123456789);
+  T (10, "%i", -123456789);         /* { dg-warning "nul past the end" } */
+  T (10, "%i",        MAX);         /* { dg-warning "nul past the end" } */
+  T (10, "%i",        MIN);         /* { dg-warning "into a region" } */
+
+  T (11, "%i",        MAX);
+  T (11, "%i",        MIN);         /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%jd", "%ji", "%jo", "%ju", and "%jx" directives
+   for the formatting of intmax_t and uintmax_t values with constant
+   arguments.  */
+
+void test_sprintf_chk_j_const (void)
+{
+#define I(x) ((__INTMAX_TYPE__)x)
+
+  T ( 1, "%ji",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ji",  I (    1));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ji",  I (   -1));      /* { dg-warning "into a region" } */
+  T ( 1, "%ji_", I (    1));      /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%ji", I (    1));      /* { dg-warning "into a region" } */
+  T ( 1, "_%ji_",I (    1));      /* { dg-warning "into a region" } */
+  T ( 1, "%jo",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ju",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%jx",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%#jx", I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%jx",  I (    1));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%#jx", I (    1));      /* { dg-warning "into a region" } */
+
+  T ( 2, "%ji",  I (    0));
+  T ( 2, "%ji",  I (    1));
+  T ( 2, "%ji",  I (    9));
+  T ( 2, "%ji",  I (   -1));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%ji",  I (   10));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%ji_", I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 2, "_%ji", I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 2, "_%ji_",I (    0));      /* { dg-warning "character ._. at offset 4 past the end" } */
+  T ( 2, "%jo",  I (    1));
+  T ( 2, "%jo",  I (    7));
+  T ( 2, "%jo",  I (  010));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%jo",  I ( 0100));      /* { dg-warning "into a region" } */
+  T ( 2, "%jx",  I (    1));
+  T ( 2, "%#jx", I (    1));      /* { dg-warning "into a region" } */
+  T ( 2, "%jx",  I (  0xa));
+  T ( 2, "%jx",  I (  0xf));
+  T ( 2, "%jx",  I ( 0x10));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%jx",  I ( 0xff));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%jx",  I (0x1ff));      /* { dg-warning "into a region" } */
+
+  T ( 3, "%ji",  I (    0));
+  T ( 3, "%ji",  I (    1));
+  T ( 3, "%ji",  I (    9));
+  T ( 3, "%ji",  I (   -9));
+  T ( 3, "%ji",  I (   10));
+  T ( 3, "%ji",  I (   99));
+  T ( 3, "%ji",  I (  -99));      /* { dg-warning "nul past the end" } */
+
+  /* ~0 is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+ji",    ~I (0));
+  T ( 3, "%-ji",    ~I (0));
+  T ( 3, "% ji",    ~I (0));
+
+  T ( 8, "%8ju",     I (1));      /* { dg-warning "nul past the end" } */
+  T ( 9, "%8ju",     I (1));
+}
+
+/* Exercise the "%ld", "%li", "%lo", "%lu", and "%lx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_l_const (void)
+{
+  T ( 1, "%li",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%li",      1L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%li",     -1L);         /* { dg-warning "into a region" } */
+  T ( 1, "%li_",     1L);         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%li",     1L);         /* { dg-warning "into a region" } */
+  T ( 1, "_%li_",    1L);         /* { dg-warning "into a region" } */
+  T ( 1, "%lo",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%lu",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%lx",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#lx",     0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%lx",      1L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#lx",     1L);         /* { dg-warning "into a region" } */
+
+  T ( 2, "%li",      0L);
+  T ( 2, "%li",      1L);
+  T ( 2, "%li",      9L);
+  T ( 2, "%li",     -1L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%li",     10L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%li_",     0L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%li",     0L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%li_",    0L);         /* { dg-warning "character ._. at offset 4 past the end" } */
+  T ( 2, "%lo",      1L);
+  T ( 2, "%lo",      7L);
+  T ( 2, "%lo",    010L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%lo",   0100L);         /* { dg-warning "into a region" } */
+  T ( 2, "%lx",      1L);
+  T ( 2, "%#lx",     1L);         /* { dg-warning "into a region" } */
+  T ( 2, "%lx",    0xaL);
+  T ( 2, "%lx",    0xfL);
+  T ( 2, "%lx",   0x10L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%lx",   0xffL);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%lx",  0x1ffL);         /* { dg-warning "into a region" } */
+
+  T ( 3, "%li",      0L);
+  T ( 3, "%li",      1L);
+  T ( 3, "%li",      9L);
+  T ( 3, "%li",     -9L);
+  T ( 3, "%li",     10L);
+  T ( 3, "%li",     99L);
+  T ( 3, "%li",    -99L);         /* { dg-warning "nul past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+li",   ~0LU);
+  T ( 3, "%-li",   ~0LU);
+  T ( 3, "% li",   ~0LU);
+
+  T ( 8, "%8lu",     1L);         /* { dg-warning "nul past the end" } */
+  T ( 9, "%8lu",     1L);
+}
+
+/* Exercise the "%lld", "%lli", "%llo", "%llu", and "%llx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_ll_const (void)
+{
+  T ( 1, "%lli",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%lli",      1LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%lli",     -1LL);     /* { dg-warning "into a region" } */
+  T ( 1, "%lli_",     1LL);     /* { dg-warning "character ._. at offset 4 past the end" } */
+  T ( 1, "_%lli",     1LL);     /* { dg-warning "into a region" } */
+  T ( 1, "_%lli_",    1LL);     /* { dg-warning "into a region" } */
+  T ( 1, "%llo",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%llu",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%llx",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%#llx",     0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%llx",      1LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%#llx",     1LL);     /* { dg-warning "into a region" } */
+
+  T ( 2, "%lli",      0LL);
+  T ( 2, "%lli",      1LL);
+  T ( 2, "%lli",      9LL);
+  T ( 2, "%lli",     -1LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%lli",     10LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%lli_",     0LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "_%lli",     0LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "_%lli_",    0LL);     /* { dg-warning "character ._. at offset 5 past the end" } */
+  T ( 2, "%llo",      1LL);
+  T ( 2, "%llo",      7LL);
+  T ( 2, "%llo",    010LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%llo",   0100LL);     /* { dg-warning "into a region" } */
+  T ( 2, "%llx",      1LL);
+  T ( 2, "%#llx",     1LL);     /* { dg-warning "into a region" } */
+  T ( 2, "%llx",    0xaLL);
+  T ( 2, "%llx",    0xfLL);
+  T ( 2, "%llx",   0x10LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%llx",   0xffLL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%llx",  0x1ffLL);     /* { dg-warning "into a region" } */
+
+  T ( 3, "%lli",      0LL);
+  T ( 3, "%lli",      1LL);
+  T ( 3, "%lli",      9LL);
+  T ( 3, "%lli",     -9LL);
+  T ( 3, "%lli",     10LL);
+  T ( 3, "%lli",     99LL);
+  T ( 3, "%lli",    -99LL);     /* { dg-warning "nul past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+lli",   ~0LLU);
+  T ( 3, "%-lli",   ~0LLU);
+  T ( 3, "% lli",   ~0LLU);
+
+  T ( 8, "%8llu",     1LL);     /* { dg-warning "nul past the end" } */
+  T ( 9, "%8llu",     1LL);
+
+  /* assume 64-bit long long.  */
+#define LLONG_MAX   9223372036854775807LL   /* 19 bytes */
+#define LLONG_MIN   (-LLONG_MAX - 1)        /* 20 bytes */
+
+  T (18, "%lli", LLONG_MIN);    /* { dg-warning "into a region" } */
+  T (19, "%lli", LLONG_MIN);    /* { dg-warning "into a region" } */
+  T (20, "%lli", LLONG_MIN);    /* { dg-warning "nul past the end" } */
+  T (21, "%lli", LLONG_MIN);
+
+  T (18, "%lli", LLONG_MAX);    /* { dg-warning "into a region" } */
+  T (19, "%lli", LLONG_MAX);    /* { dg-warning "nul past the end" } */
+  T (20, "%lli", LLONG_MAX);
+
+  T (21, "%llo",      -1LL);    /* { dg-warning "into a region" } */
+  T (22, "%llo",      -1LL);    /* { dg-warning "nul past the end" } */
+  T (23, "%llo",      -1LL);
+
+  T (19, "%llu",      -1LL);    /* { dg-warning "into a region" } */
+  T (20, "%llu",      -1LL);    /* { dg-warning "nul past the end" } */
+  T (21, "%llu",      -1LL);
+
+  T (15, "%llx",      -1LL);    /* { dg-warning "into a region" } */
+  T (16, "%llx",      -1LL);    /* { dg-warning "nul past the end" } */
+  T (17, "%llx",      -1LL);
+}
+
+void test_sprintf_chk_L_const (void)
+{
+  T (-1, "%Li",        0LL);
+  T ( 1, "%Li",        0LL);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%Li",        1LL);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%Li",       -1LL);         /* { dg-warning "into a region" } */
+  T ( 1, "%Li_",       1LL);         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%Li",       1LL);         /* { dg-warning "into a region" } */
+  T ( 1, "_%Li_",      1LL);         /* { dg-warning "into a region" } */
+}
+
+void test_sprintf_chk_z_const (void)
+{
+  T (-1, "%zi",        (size_t)0);
+  T ( 1, "%zi",        (size_t)0);  /* { dg-warning "nul past the end" } */
+  T ( 1, "%zi",        (size_t)1);  /* { dg-warning "nul past the end" } */
+  T ( 1, "%zi",        (size_t)-1L);/* { dg-warning "into a region" } */
+  T ( 1, "%zi_",       (size_t)1);  /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%zi",       (size_t)1);  /* { dg-warning "into a region" } */
+  T ( 1, "_%zi_",      (size_t)1);  /* { dg-warning "into a region" } */
+
+  T ( 2, "%zu",        (size_t)1);
+  T ( 2, "%zu",        (size_t)9);
+  T ( 2, "%zu",        (size_t)10); /* { dg-warning "nul past the end" } */
+}
+
+void test_sprintf_chk_e_const (void)
+{
+  T (-1, "%E",   0.0);
+  T ( 0, "%E",   0.0);          /* { dg-warning "into a region" } */
+  T ( 0, "%e",   0.0);          /* { dg-warning "into a region" } */
+  T ( 1, "%E",   1.0);          /* { dg-warning "into a region" } */
+  T ( 1, "%e",   1.0);          /* { dg-warning "into a region" } */
+  T ( 2, "%e",   2.0);          /* { dg-warning "into a region" } */
+  T ( 3, "%e",   3.0);          /* { dg-warning "into a region" } */
+  T (12, "%e",   1.2);          /* { dg-warning "nul past the end" } */
+  T (12, "%e",  12.0);          /* { dg-warning "nul past the end" } */
+  T (13, "%e",   1.3);          /* 1.300000e+00 */
+  T (13, "%E",  13.0);          /* 1.300000e+01 */
+  T (13, "%e",  13.0);
+  T (13, "%E",  1.4e+99);       /* 1.400000e+99 */
+  T (13, "%e",  1.5e+100);      /* { dg-warning "nul past the end" } */
+  T (14, "%E",  1.6e+101);      /* 1.600000E+101 */
+  T (14, "%e", -1.7e+102);      /* { dg-warning "nul past the end" } */
+  T (15, "%E", -1.8e+103);      /* -1.800000E+103 */
+
+  T (16, "%.8e", -1.9e+104);    /* { dg-warning "nul past the end" } */
+  T (17, "%.8e", -2.0e+105);    /* -2.00000000e+105 */
+
+  T ( 5, "%.0e", 0.0);          /* { dg-warning "nul past the end" } */
+  T ( 5, "%.0e", 1.0);          /* { dg-warning "nul past the end" } */
+  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" } */
+}
+
+/* At -Wformat-length level 1 unknown numbers are assumed to have
+   the value one, and unknown strings are assumed to have a zero
+   length.  */
+
+void test_sprintf_chk_s_nonconst (int i, const char *s)
+{
+  T (-1, "%s",   s);
+  T ( 0, "%s",   s);            /* { dg-warning "nul past the end" } */
+  T ( 1, "%s",   s);
+  T ( 1, "%.0s", s);
+  T ( 1, "%.1s", s);            /* { dg-warning "nul past the end" } */
+
+  /* The following will definitely write past the end of the buffer,
+     but since at level 1 the length of an unknown string argument
+     is assumed to be zero, it will write the terminating nul past
+     the end (we don't print "past the end" when we're not
+     sure which we can't be with an unknown string.  */
+  T (1, "%1s",  s);             /* { dg-warning "writing a terminating nul past the end" } */
+}
+
+/* Exercise the hh length modifier with all integer specifiers and
+   a non-constant argument.  */
+
+void test_sprintf_chk_hh_nonconst (int a)
+{
+  T (-1, "%hhd",        a);
+
+  T (0, "%hhd",         a);     /* { 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, "%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" } */
+
+  T (1, "% hhd",        a);     /* { dg-warning "into a region" } */
+  T (1, "% hhi",        a);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        a);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        a);     /* { dg-warning "into a region" } */
+  T (1, "%-hhd",        a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-hhi",        a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hhd",         a);
+  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, "% 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 (3, "%2hhd",        a);
+  T (3, "%2hhi",        a);
+  T (3, "%2hho",        a);
+  T (3, "%2hhu",        a);
+  T (3, "%2hhx",        a);
+
+  /* Exercise cases where the type of the actual argument (whose value
+     and range are unknown) constrain the size of the output and so
+     can be used to avoid what would otherwise be false positives.  */
+
+  T (2, "%hhd", (UChar)a);
+  T (2, "%hhi", (UChar)a);
+  T (2, "%-hhi", (UChar)a);
+}
+
+/* Exercise the h length modifier with all integer specifiers and
+   a non-constant argument.  */
+
+void test_sprintf_chk_h_nonconst (int a)
+{
+  T (-1, "%hd",         a);
+
+  T (0, "%hd",          a);     /* { dg-warning "into a region" } */
+  T (0, "%hi",          a);     /* { dg-warning "into a region" } */
+  T (0, "%hu",          a);     /* { dg-warning "into a region" } */
+  T (0, "%hx",          a);     /* { dg-warning "into a region" } */
+
+  T (1, "%hd",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hi",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hx",          a);     /* { dg-warning "nul past the end" } */
+
+  T (1, "% hd",         a);     /* { dg-warning "into a region" } */
+  T (1, "% hi",         a);     /* { dg-warning "into a region" } */
+  T (1, "%+hd",         a);     /* { dg-warning "into a region" } */
+  T (1, "%+hi",         a);     /* { dg-warning "into a region" } */
+  T (1, "%-hd",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-hi",         a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hd",          a);
+  T (2, "%hi",          a);
+  T (2, "%ho",          a);
+  T (2, "%hu",          a);
+  T (2, "%hx",          a);
+
+  T (2, "% hd",         a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hi",         a);     /* { dg-warning "nul past the end" } */
+  T (2, "% ho",         a);     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% hu",         a);     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% hx",         a);     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%ho",         a);     /* { dg-warning "nul past the end" } */
+  T (2, "#%hx",         a);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2hd",         a);
+  T (3, "%2hi",         a);
+  T (3, "%2ho",         a);
+  T (3, "%2hu",         a);
+  T (3, "%2hx",         a);
+}
+
+/* Exercise all integer specifiers with no modifier and a non-constant
+   argument.  */
+
+void test_sprintf_chk_int_nonconst (int a)
+{
+  T (-1, "%d",          a);
+
+  T (0, "%d",           a);     /* { dg-warning "into a region" } */
+  T (0, "%i",           a);     /* { dg-warning "into a region" } */
+  T (0, "%u",           a);     /* { dg-warning "into a region" } */
+  T (0, "%x",           a);     /* { dg-warning "into a region" } */
+
+  T (1, "%d",           a);     /* { dg-warning "nul past the end" } */
+  T (1, "%i",           a);     /* { dg-warning "nul past the end" } */
+  T (1, "%u",           a);     /* { dg-warning "nul past the end" } */
+  T (1, "%x",           a);     /* { dg-warning "nul past the end" } */
+
+  T (1, "% d",          a);     /* { dg-warning "into a region" } */
+  T (1, "% i",          a);     /* { dg-warning "into a region" } */
+  T (1, "%+d",          a);     /* { dg-warning "into a region" } */
+  T (1, "%+i",          a);     /* { dg-warning "into a region" } */
+  T (1, "%-d",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-i",          a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%d",           a);
+  T (2, "%i",           a);
+  T (2, "%o",           a);
+  T (2, "%u",           a);
+  T (2, "%x",           a);
+
+  T (2, "% d",          a);     /* { dg-warning "nul past the end" } */
+  T (2, "% i",          a);     /* { dg-warning "nul past the end" } */
+  T (2, "% o",          a);     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u",          a);     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x",          a);     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o",          a);     /* { dg-warning "nul past the end" } */
+  T (2, "#%x",          a);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d",          a);
+  T (3, "%2i",          a);
+  T (3, "%2o",          a);
+  T (3, "%2u",          a);
+  T (3, "%2x",          a);
+}
+
+void test_sprintf_chk_e_nonconst (double d)
+{
+  T (-1, "%E",          d);
+  T ( 0, "%E",          d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
+  T ( 0, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%E",          d);           /* { dg-warning "into a region" } */
+  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 (13, "%E",          d);           /* 1.000000E+00 */
+  T (13, "%e",          d);
+  T (14, "%E",          d);
+  T (14, "%e",          d);
+
+  T  (0, "%+E",         d);           /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */
+  T  (0, "%-e",         d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
+  T  (0, "% E",         d);           /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */
+
+  /* The range of output of "%.0e" is between 5 and 7 bytes (not counting
+     the terminating NUL.  */
+  T ( 5, "%.0e",        d);           /* { dg-warning "writing a terminating nul past the end" } */
+  T ( 6, "%.0e",        d);           /* 1e+00 */
+
+  /* The range of output of "%.1e" is between 7 and 9 bytes (not counting
+     the terminating NUL.  */
+  T ( 7, "%.1e",        d);           /* { dg-warning "writing a terminating nul past the end" } */
+  T ( 8, "%.1e",        d);
+}
+
+void test_sprintf_chk_f_nonconst (double d)
+{
+  T (-1, "%F",          d);
+  T ( 0, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 0, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 4, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 4, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 5, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 5, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 6, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 6, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 7, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 7, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 8, "%F",          d);           /* { dg-warning "nul past the end" } */
+  T ( 8, "%f",          d);           /* { dg-warning "nul past the end" } */
+  T ( 9, "%F",          d);
+  T ( 9, "%f",          d);
+}
+
+/* Tests for __builtin_vsprintf_chk are the same as those for
+   __builtin_sprintf_chk with non-constant arguments.  */
+#undef T
+#define T(size, fmt)                                                   \
+  __builtin___vsprintf_chk (buffer (size), 0, objsize (size), fmt, va)
+
+void test_vsprintf_chk_c (__builtin_va_list va)
+{
+  T (-1, "%c");
+
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+  T (0, "%c");              /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+  T (1, "%c");              /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%c");              /* { dg-warning "nul past the end" } */
+  T (2, "%c");
+  T (2, "%2c");             /* { dg-warning "nul past the end" } */
+  T (2, "%3c");             /* { dg-warning "into a region" } */
+  T (2, "%c%c");            /* { dg-warning "nul past the end" } */
+  T (3, "%c%c");
+
+  /* Wide characters.  */
+  T (0, "%lc");             /* { dg-warning "nul past the end" } */
+  T (1, "%lc");
+  T (2, "%lc");
+
+  /* The following could result in as few as a single byte and in as many
+     as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc");
+  T (2, "%1lc");
+  /* Writing some unknown number of bytes into a field two characters wide.  */
+  T (2, "%2lc");            /* { dg-warning "nul past the end" } */
+  T (2, "%lc%lc");
+
+  T (3, "%lc%c");
+  /* Here in the best case each argument will format as single character,
+     causing the terminating NUL to be written past the end.  */
+  T (3, "%lc%c%c");
+
+}
+
+void test_vsprintf_chk_int (__builtin_va_list va)
+{
+  T (-1, "%d");
+
+  T (0, "%d");                /* { dg-warning "into a region" } */
+  T (0, "%i");                /* { dg-warning "into a region" } */
+  T (0, "%u");                /* { dg-warning "into a region" } */
+  T (0, "%x");                /* { dg-warning "into a region" } */
+
+  T (1, "%d");                /* { dg-warning "nul past the end" } */
+  T (1, "%i");                /* { dg-warning "nul past the end" } */
+  T (1, "%u");                /* { dg-warning "nul past the end" } */
+  T (1, "%x");                /* { dg-warning "nul past the end" } */
+
+  T (1, "% d");               /* { dg-warning "into a region" } */
+  T (1, "% i");               /* { dg-warning "into a region" } */
+  T (1, "%+d");               /* { dg-warning "into a region" } */
+  T (1, "%+i");               /* { dg-warning "into a region" } */
+  T (1, "%-d");               /* { dg-warning "nul past the end" } */
+  T (1, "%-i");               /* { dg-warning "nul past the end" } */
+
+  T (2, "%d");
+  T (2, "%i");
+  T (2, "%o");
+  T (2, "%u");
+  T (2, "%x");
+
+  T (2, "% d");               /* { dg-warning "nul past the end" } */
+  T (2, "% i");               /* { dg-warning "nul past the end" } */
+  T (2, "% o");               /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u");               /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x");               /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o");               /* { dg-warning "nul past the end" } */
+  T (2, "#%x");               /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d");
+  T (3, "%2i");
+  T (3, "%2o");
+  T (3, "%2u");
+  T (3, "%2x");
+}
+
+#undef T
+#define T(size, fmt, ...)                                              \
+  __builtin_snprintf (buffer (size), objsize (size), fmt, __VA_ARGS__)
+
+void test_snprintf_c_const (void)
+{
+  T (-1, "%c",    0);            /* { dg-warning "specified destination size \[0-9\]+ too large" } */
+
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+
+  /* A call to snprintf with a buffer of zero size is a request to determine
+     the size of output without writing anything into the destination. No
+     warning must be issued.  */
+  T (0, "%c",     0);
+  T (1, "%c",     0);            /* { dg-warning "output truncated before the last format character" } */
+  T (1, "%c",   '1');            /* { dg-warning "output truncated" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "output truncated" } */
+  T (2, "%3c",  '1');            /* { dg-warning "directive output truncated" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "output truncated" } */
+  T (3, "%c%c", '1', '2');
+
+  /* Wide characters.  */
+  T (0, "%lc",  0);
+  T (1, "%lc",  0);
+  T (2, "%lc",  0);
+
+  /* The following could result in as few as a single byte and in as many
+     as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing at least 1 characted into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "output truncated before the last format character" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* Here in the best case each argument will format as single character,
+     causing the output to be truncated just before the terminating NUL
+     (i.e., cutting off the '3').  */
+  T (3, "%lc%c%c", L'1', '2', '3');   /* { dg-warning "output truncated" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+#undef T
+#define T(size, fmt, ...)                                              \
+  __builtin___snprintf_chk (buffer (size), objsize (size),             \
+                           0, objsize (size), fmt, __VA_ARGS__)
+
+void test_snprintf_chk_c_const (void)
+{
+  /* Verify that specifying a size of the destination buffer that's
+     bigger than its actual size (normally determined and passed to
+     the function by __builtin_object_size) is diagnosed.  */
+  __builtin___snprintf_chk (buffer, 3, 0, 2, " ");   /* { dg-warning "always overflow|specified size 3 exceeds the size 2 of the destination" } */
+
+  T (-1, "%c",    0);           /* { dg-warning "specified destination size \[^ \]* too large" } */
+
+  T (0, "%c",     0);
+  T (0, "%c%c",   0, 0);
+  T (0, "%c_%c",  0, 0);
+  T (0, "_%c_%c", 0, 0);
+
+  T (1, "%c",     0);            /* { dg-warning "output truncated before the last format character" } */
+  T (1, "%c",   '1');            /* { dg-warning "output truncated" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "output truncated" } */
+  T (2, "%3c",  '1');            /* { dg-warning "directive output truncated" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "output truncated before the last format character" } */
+  T (3, "%c%c", '1', '2');
+  T (3, "%c_%c", '1', '2');      /* { dg-warning "output truncated" } */
+
+  /* Wide characters.  */
+  T (0, "%lc",  0);
+  T (1, "%lc",  0);
+  T (2, "%lc",  0);
+
+  /* The following could result in as few as a single byte and in as many
+     as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing at least 1 characted into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "output truncated before the last format character" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* Here in the best case each argument will format as single character,
+     causing the output to be truncated just before the terminating NUL
+     (i.e., cutting off the '3').  */
+  T (3, "%lc%c%c", L'1', '2', '3');   /* { dg-warning "output truncated" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+/* Macro to verify that calls to __builtin_vsprintf (i.e., with no size
+   argument) issue diagnostics by correctly determining the size of
+   the destination buffer.  */
+#undef T
+#define T(size, fmt)                           \
+  __builtin_vsprintf (buffer (size), fmt, va)
+
+void test_vsprintf_s (__builtin_va_list va)
+{
+  T (-1, "%s");
+
+  T (0, "%s");              /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "writing a terminating nul past the end" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "writing a terminating nul past the end" } */
+}
+
+/* Exercise all integer specifiers with no modifier and a non-constant
+   argument.  */
+
+void test_vsprintf_int (__builtin_va_list va)
+{
+  T (-1, "%d");
+
+  T (0, "%d");     /* { dg-warning "into a region" } */
+  T (0, "%i");     /* { dg-warning "into a region" } */
+  T (0, "%u");     /* { dg-warning "into a region" } */
+  T (0, "%x");     /* { dg-warning "into a region" } */
+
+  T (1, "%d");     /* { dg-warning "nul past the end" } */
+  T (1, "%i");     /* { dg-warning "nul past the end" } */
+  T (1, "%u");     /* { dg-warning "nul past the end" } */
+  T (1, "%x");     /* { dg-warning "nul past the end" } */
+
+  T (1, "% d");     /* { dg-warning "into a region" } */
+  T (1, "% i");     /* { dg-warning "into a region" } */
+  T (1, "%+d");     /* { dg-warning "into a region" } */
+  T (1, "%+i");     /* { dg-warning "into a region" } */
+  T (1, "%-d");     /* { dg-warning "nul past the end" } */
+  T (1, "%-i");     /* { dg-warning "nul past the end" } */
+
+  T (2, "%d");
+  T (2, "%i");
+  T (2, "%o");
+  T (2, "%u");
+  T (2, "%x");
+
+  T (2, "% d");     /* { dg-warning "nul past the end" } */
+  T (2, "% i");     /* { dg-warning "nul past the end" } */
+  T (2, "% o");     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u");     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x");     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o");     /* { dg-warning "nul past the end" } */
+  T (2, "#%x");     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d");
+  T (3, "%2i");
+  T (3, "%2o");
+  T (3, "%2u");
+  T (3, "%2x");
+}
+
+#undef T
+#define T(size, fmt)                                                   \
+  __builtin_vsnprintf (buffer (size), objsize (size), fmt, va)
+
+void test_vsnprintf_s (__builtin_va_list va)
+{
+  T (-1, "%s");             /* { dg-warning "specified destination size \[^ \]* too large" } */
+
+  T (0, "%s");
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "output truncated before the last format character" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "output truncated before the last format character" } */
+}
+
+#undef T
+#define T(size, fmt)                                                   \
+  __builtin___vsnprintf_chk (buffer (size), objsize (size),            \
+                            0, objsize (size), fmt, va)
+
+void test_vsnprintf_chk_s (__builtin_va_list va)
+{
+  /* Verify that specifying a size of the destination buffer that's
+     bigger than its actual size (normally determined and passed to
+     the function by __builtin_object_size) is diagnosed.  */
+  __builtin___snprintf_chk (buffer, 123, 0, 122, " ");   /* { dg-warning "always overflow|specified size 123 exceeds the size 122 of the destination object" } */
+
+  __builtin___snprintf_chk (buffer, __SIZE_MAX__, 0, 2, " ");   /* { dg-warning "always overflow|destination size .\[0-9\]+. too large" } */
+
+  T (0, "%s");
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "output truncated before the last format character" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "output truncated before the last format character" } */
+}
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
new file mode 100644 (file)
index 0000000..b0f2d45
--- /dev/null
@@ -0,0 +1,214 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
+
+/* When debugging, define LINE to the line number of the test case to exercise
+   and avoid exercising any of the others.  The buffer and objsize macros
+   below make use of LINE to avoid warnings for other lines.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+char buffer [256];
+extern char *ptr;
+
+#define buffer(size)                                                   \
+  (!LINE || __LINE__ == LINE ? buffer + sizeof buffer - size : ptr)
+
+#define objsize(size)  (!LINE || __LINE__ == LINE ? size : __SIZE_MAX__ / 2)
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+#define T(size, fmt, ...)                              \
+  __builtin_sprintf (buffer (size), fmt, __VA_ARGS__)
+
+__builtin_va_list va;
+
+/* Exercise buffer overflow detection with const string arguments.  */
+
+void test_s_const (void)
+{
+    /* Wide string literals are handled slightly differently than
+       at level 1.  At level 1, each wide character is assumed to
+       convert into a single byte.  At level 2, they are assumed
+       to convert into at least one byte.  */
+  T (0, "%ls",      L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%ls",      L"");
+  T (1, "%ls",      L"\0");
+  T (1, "%1ls",     L"");       /* { dg-warning "nul past the end" } */
+
+  T (0, "%*ls",  0, L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%*ls",  0, L"");
+  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, "%.0ls",    L"1");
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+
+  /* 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
+     character), and at most 2 bytes because of the precision.  Since its
+     output is explicitly bounded it is diagnosed.  */
+  T (2, "%.2ls",    L"1");      /* { dg-warning "nul past the end" } */
+  T (2, "%.*ls", 2, L"1");      /* { dg-warning "nul past the end" } */
+
+  /* The following three are constrained by the precision to at most
+     that many bytes of the converted wide string plus a terminating NUL.  */
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+  T (3, "%.2ls",    L"1");
+}
+
+
+struct Arrays {
+  char a1 [1];
+  char a2 [2];
+  char a3 [3];
+  char a4 [4];
+  char a0 [0];
+  char ax [];
+};
+
+/* Exercise buffer overflow detection with non-const string arguments.  */
+
+void test_s_nonconst (const char *s, const wchar_t *ws, struct Arrays *a)
+{
+  T (0, "%s",   s);             /* { dg-warning "into a region" "sprintf transformed into strcpy" { xfail *-*-*-* } } */
+  T (1, "%s",   s);             /* { dg-warning "nul past the end" "sprintf transformed into strcpy" { xfail *-*-*-* } } */
+  T (1, "%1s",  s);             /* { dg-warning "nul past the end" } */
+  T (1, "%.0s", s);
+  T (1, "%.1s", s);             /* { 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.  */
+  T (1, "%-s", a->a1);          /* { dg-warning "nul past the end" } */
+
+  /* In the following test, since the length of the strings isn't known,
+     their type (the array) is used to bound the maximum length to 1,
+     which means the "%-s" directive would not overflow the buffer,
+     but it would leave no room for the terminating nul.  */
+  T (1, "%-s", a->a2);          /* { dg-warning "writing a terminating nul" } */
+
+  /* Unlike in the test above, since the length of the string is bounded
+     by the array type to at most 2, the "^-s" directive is diagnosed firts,
+     preventing the diagnostic about the terminatinb nul.  */
+  T (1, "%-s", a->a3);          /* { dg-warning "directive writing between 1 and 2 bytes" } */
+
+  /* The length of a zero length array and flexible array member is
+     unknown and at leve 2 assumed to be at least 1.  */
+  T (1, "%-s", a->a0);          /* { dg-warning "nul past the end" } */
+  T (1, "%-s", a->ax);          /* { dg-warning "nul past the end" } */
+
+  T (2, "%-s", a->a0);
+  T (2, "%-s", a->ax);
+}
+
+  /* Exercise buffer overflow detection with non-const integer arguments.  */
+
+void test_hh_nonconst (int x)
+{
+  T (1, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (2, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (3, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (4, "%hhi",         x);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
+
+void test_h_nonconst (int x)
+{
+  extern UChar uc;
+
+  T (1, "%hi",         uc);     /* { dg-warning "into a region" } */
+  T (2, "%hi",         uc);     /* { dg-warning "into a region" } */
+  /* Formatting an 8-bit unsigned char as a signed short (or any other
+     type with greater precision) can write at most 3 characters.  */
+  T (3, "%hi",         uc);     /* { dg-warning "terminating nul past" } */
+  T (4, "%hi",         uc);
+
+  /* Verify that the same thing works when the int argument is cast
+     to unsigned char.  */
+  T (1, "%hi",   (UChar)x);     /* { dg-warning "into a region" } */
+  T (2, "%hi",   (UChar)x);     /* { dg-warning "into a region" } */
+  T (3, "%hi",   (UChar)x);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T (4, "%hi",   (UChar)x);
+}
+
+void test_i_nonconst (int x)
+{
+  extern UChar uc;
+
+  T (1, "%i",          uc);     /* { dg-warning "into a region" } */
+  T (2, "%i",          uc);     /* { dg-warning "into a region" } */
+  T (3, "%i",          uc);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",          uc);
+
+  T (1, "%i",    (UChar)x);     /* { dg-warning "into a region" } */
+  T (2, "%i",    (UChar)x);     /* { dg-warning "into a region" } */
+  T (3, "%i",    (UChar)x);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",    (UChar)x);
+
+  /* Verify the same thing using a bit-field.  */
+  extern struct {
+    unsigned int  b1: 1;
+    unsigned int  b2: 2;
+    unsigned int  b3: 3;
+    unsigned int  b4: 4;
+            int sb4: 4;
+    unsigned int  b5: 5;
+    unsigned int  b6: 6;
+    unsigned int  b7: 7;
+    unsigned int  b8: 8;
+  } bf, abf[], *pbf;
+
+  T (1, "%i",       bf.b1);     /* { dg-warning "nul past the end" } */
+  T (1, "%i",  abf [x].b1);     /* { dg-warning "nul past the end" } */
+  T (1, "%i",     pbf->b1);     /* { dg-warning "nul past the end" } */
+  /* A one bit bit-field can only be formatted as '0' or '1'.  Similarly,
+     two- and three-bit bit-fields can only be formatted as a single
+     decimal digit.  */
+  T (2, "%i",       bf.b1);
+  T (2, "%i",  abf [x].b1);
+  T (2, "%i",     pbf->b1);
+  T (2, "%i",       bf.b2);
+  T (2, "%i",  abf [x].b2);
+  T (2, "%i",     pbf->b2);
+  T (2, "%i",       bf.b3);
+  T (2, "%i",  abf [x].b3);
+  T (2, "%i",     pbf->b3);
+  /* A four-bit bit-field can be formatted as either one or two digits.  */
+  T (2, "%i",       bf.b4);     /* { dg-warning "nul past the end" } */
+  T (2, "%i",  abf [x].b4);     /* { dg-warning "nul past the end" } */
+  T (2, "%i",     pbf->b4);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%i",       bf.b4);
+  T (3, "%i",     pbf->b4);
+  T (3, "%i",       bf.b5);
+  T (3, "%i",     pbf->b5);
+  T (3, "%i",       bf.b6);
+  T (3, "%i",     pbf->b6);
+  T (3, "%i",       bf.b7);     /* { dg-warning "nul past the end" } */
+  T (3, "%i",     pbf->b7);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (2, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  /* Formatting an 8-bit unsigned char as a signed short (or any other
+     type with greater precision) int can write at most 3 characters.  */
+  T (3, "%i",       bf.b8);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",       bf.b8);
+
+  T (1, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (2, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (3, "%i",       bf.b8);     /* { dg-warning "terminating nul past" } */
+
+  T (2, "%i",      bf.sb4);     /* { dg-warning "terminating nul past" } */
+  T (3, "%i",      bf.sb4);
+  T (4, "%i",      bf.sb4);
+}
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
new file mode 100644 (file)
index 0000000..625d055
--- /dev/null
@@ -0,0 +1,234 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+#ifndef LINE
+#  define LINE 0
+#endif
+
+#define bos(x) __builtin_object_size (x, 0)
+
+#define T(bufsize, fmt, ...)                                           \
+    do {                                                               \
+      if (!LINE || __LINE__ == LINE)                                   \
+       {                                                               \
+         char *d = (char *)__builtin_malloc (bufsize);                 \
+         __builtin___sprintf_chk (d, 0, bos (d), fmt, __VA_ARGS__);    \
+         sink (d);                                                     \
+       }                                                               \
+    } while (0)
+
+void
+sink (void*);
+
+/* Identity function to verify that the checker figures out the value
+   of the operand even when it's not constant (i.e., makes use of
+   inlining and constant propagation information).  */
+
+int i (int x) { return x; }
+const char* s (const char *str) { return str; }
+
+/* Function to "generate" a unique unknown number (as far as GCC can
+   tell) each time it's called.  It prevents the optimizer from being
+   able to narrow down the ranges of possible values in test functions
+   with repeated references to the same variable.  */
+extern int x (void);
+
+/* Verify that the checker can detect buffer overflow when the "%s"
+   argument is in a known range of lengths and one or both of which
+   exceed the size of the destination.  */
+
+void test_sprintf_chk_string (const char *s, const char *t)
+{
+#define x x ()
+
+  T (1, "%s", x ? "" : "1");       /* { dg-warning "nul past the end" } */
+  T (1, "%s", x ? "1" : "");       /* { dg-warning "nul past the end" } */
+  T (1, "%s", x ? s : "1");        /* { dg-warning "nul past the end" } */
+  T (1, "%s", x ? "1" : s);        /* { dg-warning "nul past the end" } */
+  T (1, "%s", x ? s : t);
+
+  T (2, "%s", x ? "" : "1");
+  T (2, "%s", x ? "" : s);
+  T (2, "%s", x ? "1" : "");
+  T (2, "%s", x ? s : "");
+  T (2, "%s", x ? "1" : "2");
+  T (2, "%s", x ? "" : "12");      /* { dg-warning "nul past the end" } */
+  T (2, "%s", x ? "12" : "");      /* { dg-warning "nul past the end" } */
+
+  T (2, "%s", x ? "" : "123");     /* { dg-warning "into a region" } */
+  T (2, "%s", x ? "123" : "");     /* { dg-warning "into a region" } */
+
+#undef x
+}
+
+
+/* Verify that the checker makes use of integer constant propagation
+   to detect buffer overflow in non-constant cases.  */
+
+void test_sprintf_chk_integer_value (void)
+{
+  T ( 1, "%i",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",  i (    1));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",  i (   -1));         /* { dg-warning "into a region" } */
+  T ( 1, "%i_", i (    1));         /* { dg-warning "character ._. at offset 2 past the end" } */
+  T ( 1, "_%i", i (    1));         /* { dg-warning "into a region" } */
+  T ( 1, "_%i_",i (    1));         /* { dg-warning "into a region" } */
+  T ( 1, "%o",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%u",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x", i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",  i (    1));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x", i (    1));         /* { dg-warning "into a region" } */
+
+  T ( 2, "%i",  i (    0));
+  T ( 2, "%i",  i (    1));
+  T ( 2, "%i",  i (    9));
+  T ( 2, "%i",  i (   -1));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  i (   10));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i_", i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i", i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i_",i (    0));         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 2, "%o",  i (    1));
+  T ( 2, "%o",  i (    7));
+  T ( 2, "%o",  i (  010));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%o",  i ( 0100));         /* { dg-warning "into a region" } */
+  T ( 2, "%x",  i (    1));
+  T ( 2, "%#x", i (    1));         /* { dg-warning "into a region" } */
+  T ( 2, "%x",  i (  0xa));
+  T ( 2, "%x",  i (  0xf));
+  T ( 2, "%x",  i ( 0x10));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",  i ( 0xff));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",  i (0x1ff));         /* { dg-warning "into a region" } */
+
+  T ( 3, "%i",  i (    0));
+  T ( 3, "%i",  i (    1));
+  T ( 3, "%i",  i (    9));
+  T ( 3, "%i",  i (   -9));
+  T ( 3, "%i",  i (   10));
+  T ( 3, "%i",  i (   99));
+  T ( 3, "%i",  i (  -99));         /* { dg-warning "nul past the end" } */
+
+  T ( 3, "%i",  i (99) + i (1));    /* { dg-warning "nul past the end" } */
+
+  T ( 8, "%8u", i (    1));         /* { dg-warning "nul past the end" } */
+  T ( 9, "%8u", i (    1));
+}
+
+/* Functions to require optimization to figure out the range of the operand.
+   Used to verify that the checker makes use of the range information to
+   avoid diagnosing the output of sufficiently constrained arguments to
+   integer directives.  */
+
+signed char*
+range_schar (signed char *val, signed char min, signed char max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+unsigned char*
+range_uchar (unsigned char *val, unsigned char min, unsigned char max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+signed short*
+range_sshort (signed short *val, signed short min, signed short max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+unsigned short*
+range_ushort (unsigned short *val, unsigned short min, unsigned short max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+/* Helper to prevent GCC from figuring out the return value.  */
+extern int idx (void);
+
+/* Exercise ranges only in types signed and unsigned char and short.
+   No other types work due to bug 71690.  */
+
+void test_sprintf_chk_range_schar (signed char *a)
+{
+  (void)&a;
+
+  /* Ra creates a range of signed char for A [idx].  A different
+     value is used each time to prevent the ranges from intesecting
+     one another, possibly even eliminating some tests as a result
+     of the range being empty.  */
+#define R(min, max) *range_schar (a + idx (), min, max)
+
+  T ( 0, "%i",  R (0, 9));      /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%i",  R (0, 9));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  R (0, 9));
+  T ( 2, "%i",  R (-1, 0));     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T ( 2, "%i",  R (9, 10));     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 3, "%i",  R ( -9,   9));
+  T ( 3, "%i",  R (-99,  99));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T ( 3, "%i",  R (  0,  99));
+  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
+     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 ( 4, "%i%i", R (10, 11), R (12, 13));   /* { dg-warning "nul past the end" } */
+
+  T ( 5, "%i%i", R (-9, 99), R (-9, 99));
+
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0,  9));
+  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" } */
+}
+
+void test_sprintf_chk_range_uchar (unsigned char *a, unsigned char *b)
+{
+  (void)&a;
+  (void)&b;
+
+#undef Ra
+#define Ra(min, max) *range_uchar (a + idx (), min, max)
+
+  T ( 0, "%i",  Ra (0,  9));   /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%i",  Ra (0,  9));   /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  Ra (0,  9));
+  T ( 2, "%i",  Ra (9, 10));   /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 3, "%i",  Ra (0,  99));
+  T ( 3, "%i",  Ra (0, 100));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
+
+void test_sprintf_chk_range_sshort (signed short *a, signed short *b)
+{
+  (void)&a;
+  (void)&b;
+
+#undef Ra
+#define Ra(min, max) *range_sshort (a + idx (), min, max)
+
+  T ( 0, "%i",  Ra ( 0, 9));     /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%i",  Ra ( 0, 1));     /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",  Ra ( 0, 9));     /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  Ra ( 0, 1));
+  T ( 2, "%i",  Ra ( 8, 9));
+  T ( 2, "%i",  Ra ( 0, 9));
+  T ( 2, "%i",  Ra (-1, 0));     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T ( 2, "%i",  Ra ( 9, 10));    /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 3, "%i",  Ra ( 0, 99));
+  T ( 3, "%i",  Ra (99, 999));   /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 4, "%i",  Ra (  0,  999));
+  T ( 4, "%i",  Ra ( 99,  999));
+  T ( 4, "%i",  Ra (998,  999));
+  T ( 4, "%i",  Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
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
new file mode 100644 (file)
index 0000000..944c36e
--- /dev/null
@@ -0,0 +1,33 @@
+/* { dg-do compile } */
+/* { dg-options "-Wformat -Wformat-length=1 -fdiagnostics-show-caret -ftrack-macro-expansion=0" } */
+
+extern int sprintf (char*, const char*, ...);
+
+char dst [8];
+
+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 }
+     { dg-begin-multiline-output "" }
+   sprintf (dst + 7, "%-s", "1");
+                     ^~~~~
+     { dg-end-multiline-output "" }
+     { dg-begin-multiline-output "" }
+   sprintf (dst + 7, "%-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 }
+     { dg-begin-multiline-output "" }
+   sprintf (dst + 7, "%-s", "abcd");
+                      ^~~   ~~~~~~
+     { dg-end-multiline-output "" }
+     { dg-begin-multiline-output "" }
+   sprintf (dst + 7, "%-s", "abcd");
+   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
new file mode 100644 (file)
index 0000000..1e50be1
--- /dev/null
@@ -0,0 +1,540 @@
+/* Test to verify that the return value of calls to __builtin_sprintf
+   that produce a known number of bytes on output is available for
+   constant folding.  With optimization enabled the test will fail to
+   link if any of the assertions fails.  Without optimization the test
+   aborts at runtime if any of the assertions fails.  */
+/* { dg-do run } */
+/* { dg-additional-options "-O2 -Wall -Wno-pedantic -fprintf-return-value" } */
+
+#ifndef LINE
+#  define LINE   0
+#endif
+
+#if __STDC_VERSION__ < 199901L
+#  define __func__   __FUNCTION__
+#endif
+
+typedef __SIZE_TYPE__ size_t;
+
+unsigned ntests;
+unsigned nfails;
+
+void __attribute__ ((noclone, noinline))
+checkv (const char *func, int line, int res, int min, int max,
+       char *dst, const char *fmt, __builtin_va_list va)
+{
+  int n = __builtin_vsprintf (dst, fmt, va);
+  int len = __builtin_strlen (dst);
+
+  ++ntests;
+
+  int fail = 0;
+  if (n != res)
+    {
+      __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" "
+                       "doesn't match function call return value: ",
+                       func, line, fmt, dst);
+      if (min == max)
+       __builtin_printf ("%i != %i\n", n, min);
+      else
+       __builtin_printf ("%i not in [%i, %i]\n", n, min, max);
+
+      fail = 1;
+    }
+  else
+    {
+      if (len < min || max < len)
+       {
+         __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" "
+                           "doesn't match output length: ",
+                           func, line, fmt, dst);
+
+         if (min == max)
+           __builtin_printf ("%i != %i\n", len, min);
+         else
+           __builtin_printf ("%i not in [%i, %i]\n", len, min, max);
+
+         fail = 1;
+       }
+      else
+       __builtin_printf ("PASS: %s:%i: \"%s\" result %i: \"%s\"\n",
+                         func, line, fmt, n, dst);
+    }
+
+  if (fail)
+    ++nfails;
+}
+
+void __attribute__ ((noclone, noinline))
+check (const char *func, int line, int res, int min, int max,
+       char *dst, const char *fmt, ...)
+{
+  __builtin_va_list va;
+  __builtin_va_start (va, fmt);
+  checkv (func, line, res, min, max, dst, fmt, va);
+  __builtin_va_end (va);
+}
+
+char buffer[256];
+char* volatile dst = buffer;
+char* ptr = buffer;
+
+#define concat(a, b)   a ## b
+#define CAT(a, b)      concat (a, b)
+
+#if __OPTIMIZE__
+/* With optimization references to the following undefined symbol which
+   is unique for each test case are expected to be eliminated.  */
+#  define TEST_FAILURE(line, ignore1, ignore2, ignore3)                        \
+  do {                                                                 \
+    extern void CAT (failure_on_line_, line)(void);                    \
+    CAT (failure_on_line_, line)();                                    \
+  } while (0)
+#else
+/* The test is run by DejaGnu with optimization enabled.  When it's run
+   with it disabled (i.e., at -O0) each test case is verified at runtime
+   and the test aborts just before exiting if any of them failed.  */
+#  define TEST_FAILURE(line, result, min, max)                         \
+  if (min == max)                                                      \
+    __builtin_printf ("FAIL: %s:%i: expected %i, got %i\n",            \
+                     __func__, line, min, result);                     \
+  else                                                                 \
+    __builtin_printf ("FAIL: %s:%i: expected range [%i, %i], got %i\n",        \
+                     __func__, line, min, max, result);
+#endif
+
+/* Verify that the result is exactly equal to RES.  */
+#define EQL(expect, size, fmt, ...)                                    \
+  if (!LINE || LINE == __LINE__)                                       \
+    do {                                                               \
+      char *buf = (size) < 0 ? ptr : buffer + sizeof buffer - (size);  \
+      int result = __builtin_sprintf (buf, fmt, __VA_ARGS__);          \
+      if (result != expect)                                            \
+       {                                                               \
+         TEST_FAILURE (__LINE__, expect, expect, result);              \
+       }                                                               \
+      check (__func__, __LINE__, result, expect, expect, dst, fmt,     \
+            __VA_ARGS__);                                              \
+    } while (0)
+
+/* Verify that the result is in the range [MIN, MAX].  */
+#define RNG(min, max, size, fmt, ...)                                  \
+  if (!LINE || LINE == __LINE__)                                       \
+    do {                                                               \
+      char *buf = (size) < 0 ? ptr : buffer + sizeof buffer - (size);  \
+      int result = __builtin_sprintf (buf, fmt, __VA_ARGS__);          \
+      if (result < min || max < result)                                        \
+       {                                                               \
+         TEST_FAILURE (__LINE__, min, max, result);                    \
+       }                                                               \
+      check (__func__, __LINE__, result, min, max, dst, fmt,           \
+            __VA_ARGS__);                                              \
+    } while (0)
+
+static void __attribute__ ((noinline, noclone))
+test_c (char c)
+{
+  EQL (1,  2, "%c",       c);
+  EQL (1, -1, "%c",       c);
+  EQL (1,  2, "%1c",      c);
+  EQL (1, -1, "%1c",      c);
+  EQL (1,  2, "%*c",      1, c);
+  EQL (1, -1, "%*c",      1, c);
+  EQL (2,  3, "%c%c",     '1', '2');
+  EQL (2, -1, "%c%c",     '1', '2');
+  EQL (3,  4, "%3c",      c);
+  EQL (3, -1, "%3c",      c);
+  EQL (3,  4, "%*c",      3, c);
+  EQL (3, -1, "%*c",      3, c);
+
+  EQL (3,  4, "%*c%*c",     2,  c,  1,  c);
+  EQL (3,  4, "%*c%*c",     1,  c,  2,  c);
+  EQL (3,  4, "%c%c%c",        '1',    '2',    '3');
+  EQL (3,  4, "%*c%c%c",    1, '1',    '2',    '3');
+  EQL (3,  4, "%*c%*c%c",   1, '1', 1, '2',    '3');
+  EQL (3,  4, "%*c%*c%*c",  1, '1', 1, '2', 1, '3');
+
+  EQL (3, -1, "%*c%*c",     2,  c,  1,  c);
+  EQL (3, -1, "%*c%*c",     1,  c,  2,  c);
+  EQL (3, -1, "%c%c%c",        '1',    '2',    '3');
+  EQL (3, -1, "%*c%c%c",    1, '1',    '2',    '3');
+  EQL (3, -1, "%*c%*c%c",   1, '1', 1, '2',    '3');
+  EQL (3, -1, "%*c%*c%*c",  1, '1', 1, '2', 1, '3');
+
+  EQL (4,  5, "%c%c %c",  '1', '2', '3');
+  EQL (5,  6, "%c %c %c", '1', '2', '3');
+  EQL (5,  6, "%c %c %c",  c,   c,   c);
+}
+
+/* Generate a pseudo-random value in the specified range.  The return
+   value must be unsigned char to work around limitations in the GCC
+   range information.  Similarly for the declaration of rand() whose
+   correct return value should be int, but that also prevents the range
+   information from making it to the printf pass.  */
+
+unsigned char uchar_range (unsigned min, unsigned max)
+{
+  extern unsigned rand (void);
+
+  unsigned x;
+  x = rand ();
+
+  if (x < min)
+    x = min;
+  else if (max < x)
+    x = max;
+
+  return x;
+}
+
+static void __attribute__ ((noinline, noclone))
+test_d_i (int i, long li)
+{
+  /*    +-------------------------- expected return value */
+  /*    |   +---------------------- destination size */
+  /*    |   |  +------------------- format string */
+  /*    |   |  |                +-- variable argument(s) */
+  /*    |   |  |                | */
+  /*    V   V  V                V */
+  EQL ( 1,  2, "%d",            0);
+  EQL ( 2,  3, "%d%d",          0,   1);
+  EQL ( 3,  4, "%d%d",          9,  10);
+  EQL ( 4,  5, "%d%d",         11,  12);
+  EQL ( 5,  6, "%d:%d",        12,  34);
+  EQL ( 5,  6, "%d",           12345);
+  EQL ( 6,  7, "%d",          -12345);
+  EQL (15, 16, "%d:%d:%d:%d", 123, 124, 125, 126);
+
+  EQL ( 1,  2, "%i", uchar_range (0, 9));
+  EQL ( 1, -1, "%i", uchar_range (0, 9));
+
+  /* The range information available to passes other than the Value
+     Range Propoagation pass itself is so bad that the following two
+     tests fail (the range seen in the test below is [0, 99] rather
+     than [10, 99].
+  EQL ( 2,  3, "%i", uchar_range (10, 99));
+  EQL ( 3,  4, "%i", uchar_range (100, 199));
+  */
+
+  /* Verify that the width allows the return value in the following
+     calls can be folded despite the unknown value of the argument.  */
+#if __SIZEOF_INT__ == 2
+  EQL ( 6,  7, "%6d",      i);
+  EQL ( 6,  7, "%+6d",     i);
+  EQL ( 6,  7, "%-6d",     i);
+  EQL ( 6,  7, "%06d",     i);
+#elif __SIZEOF_INT__ == 4
+  EQL (11, 12, "%11d",     i);
+  EQL (11, 12, "%+11d",    i);
+  EQL (11, 12, "%-11d",    i);
+  EQL (11, 12, "%011d",    i);
+#elif __SIZEOF_INT__ == 8
+  EQL (20, 21, "%20d",     i);
+  EQL (20, 21, "%+20d",    i);
+  EQL (20, 21, "%-29d",    i);
+  EQL (20, 21, "%020d",    i);
+#endif
+
+#if __SIZEOF_LONG__ == 2
+  EQL ( 6,  7, "%6ld",      li);
+  EQL ( 6,  7, "%+6ld",     li);
+  EQL ( 6,  7, "%-6ld",     li);
+  EQL ( 6,  7, "%06ld",     li);
+#elif __SIZEOF_LONG__ == 4
+  EQL (11, 12, "%11ld",     li);
+  EQL (11, 12, "%+11ld",    li);
+  EQL (11, 12, "%-11ld",    li);
+  EQL (11, 12, "%011ld",    li);
+#elif __SIZEOF_LONG__ == 8
+  EQL (20, 21, "%20ld",     li);
+  EQL (20, 21, "%+20ld",    li);
+  EQL (20, 21, "%-20ld",    li);
+  EQL (20, 21, "%020ld",    li);
+#endif
+
+  /* Verify that the output of a directive with an unknown argument
+     is correctly determined at compile time to be in the expected
+     range.  */
+
+  /*    +---------------------------- expected minimum return value */
+  /*    |   +------------------------ expected maximum return value */
+  /*    |   |   +-------------------- destination size */
+  /*    |   |   |  +----------------- format string */
+  /*    |   |   |  |           +----- variable argument(s) */
+  /*    |   |   |  |           | */
+  /*    V   V   V  V           V */
+  RNG ( 1,  4,  5, "%hhi",     i);
+  RNG ( 1,  3,  4, "%hhu",     i);
+
+#if __SIZEOF_SHORT__ == 2
+  RNG ( 1,  6,  7, "%hi",      i);
+  RNG ( 1,  5,  6, "%hu",      i);
+#elif __SIZEOF_SHORT__ == 4
+  RNG ( 1, 11, 12, "%hi",      i);
+  RNG ( 1, 10, 11, "%hu",      i);
+#endif
+
+#if __SIZEOF_INT__ == 2
+  RNG ( 1,  6,  7, "%i",       i);
+  RNG ( 1,  5,  6, "%u",       i);
+#elif __SIZEOF_INT__ == 4
+  RNG ( 1, 11, 12, "%i",       i);
+  RNG ( 1, 10, 11, "%u",       i);
+#elif __SIZEOF_INT__ == 8
+  RNG ( 1, 20, 21, "%i",       i);
+  RNG ( 1, 19, 20, "%u",       i);
+#endif
+
+#if __SIZEOF_LONG__ == 4
+  RNG ( 1, 11, 12, "%li",      li);
+  RNG ( 1, 10, 11, "%lu",      li);
+#elif __SIZEOF_LONG__ == 8
+  RNG ( 1, 20, 21, "%li",      li);
+  RNG ( 1, 19, 20, "%lu",      li);
+#endif
+}
+
+static void __attribute__ ((noinline, noclone))
+test_x (unsigned char uc, unsigned short us, unsigned ui)
+{
+  EQL ( 1,  2, "%hhx",          0);
+  EQL ( 2,  3, "%2hhx",         0);
+  EQL ( 2,  3, "%02hhx",        0);
+  EQL ( 2,  3, "%#02hhx",       0);
+
+  EQL ( 1,  2, "%hhx",          1);
+  EQL ( 2,  3, "%2hhx",         1);
+  EQL ( 2,  3, "%02hhx",        1);
+  EQL ( 3,  4, "%#02hhx",       1);
+
+  EQL ( 2,  3, "%2hhx",        uc);
+  EQL ( 2,  3, "%02hhx",       uc);
+  EQL ( 5,  6, "%#05hhx",      uc);
+
+  EQL ( 2,  3, "%2hhx",        us);
+  EQL ( 2,  3, "%02hhx",       us);
+  EQL ( 5,  6, "%#05hhx",      us);
+
+  EQL ( 2,  3, "%2hhx",        ui);
+  EQL ( 2,  3, "%02hhx",       ui);
+  EQL ( 5,  6, "%#05hhx",      ui);
+
+  EQL ( 1,  2, "%x",            0);
+  EQL ( 1,  2, "%#x",           0);
+  EQL ( 1,  2, "%#0x",          0);
+  EQL ( 1,  2, "%x",            1);
+  EQL ( 1,  2, "%x",          0xf);
+  EQL ( 2,  3, "%x",         0x10);
+  EQL ( 2,  3, "%x",         0xff);
+  EQL ( 3,  4, "%x",        0x100);
+
+  EQL (11, 12, "%02x:%02x:%02x:%02x",         0xde, 0xad, 0xbe, 0xef);
+
+  /* The following would be optimized if the range information of
+  the variable's type was made available.  Alas, it's lost due
+  to the promotion of the actual argument (unsined char) to
+  the type of the "formal" argument (int in the case of the
+  ellipsis).
+  EQL (11, 12, "%02x:%02x:%02x:%02x",   uc,   uc,   uc,   uc);
+  */
+  EQL (11, 12, "%02hhx:%02hhx:%02hhx:%02hhx",   uc,   uc,   uc,   uc);
+
+#if __SIZEOF_SHORT__ == 2
+  EQL ( 4,  5, "%04hx",                   us);
+  EQL ( 9, 10, "%04hx:%04hx",             us, us);
+  EQL (14, 15, "%04hx:%04hx:%04hx",       us, us, us);
+  EQL (19, 20, "%04hx:%04hx:%04hx:%04hx", us, us, us, us);
+#endif
+
+#if __SIZEOF_INT__ == 2
+  EQL ( 4,  5, "%04x", ui);
+  EQL ( 6,  7, "%#06x", ui);
+#elif __SIZEOF_INT__ == 4
+  EQL ( 8,  9, "%08x", ui);
+  EQL (10, 10 + 1, "%#010x", ui);
+#elif __SIZEOF_INT__ == 8
+  EQL (16, 17, "%016x", ui);
+  EQL (18, 19, "%#018x",  ui);
+#endif
+}
+
+static void __attribute__ ((noinline, noclone))
+test_a_double (void)
+{
+  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 ( 8,  9, "%.1a", 3.0);        /* 0xc.0p-2 */
+  EQL ( 9, 10, "%.2a", 4.0);        /* 0xa.00p-1 */
+}
+
+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 ( 8,  9, "%.1La", 3.0L);      /* 0xc.0p-2 */
+  EQL ( 9, 10, "%.2La", 4.0L);      /* 0xa.00p-1 */
+}
+
+static void __attribute__ ((noinline, noclone))
+test_e_double (void)
+{
+  EQL (12, 13, "%e",  1.0e0);
+  EQL (13, 14, "%e", -1.0e0);
+  EQL (12, 13, "%e",  1.0e+1);
+  EQL (13, 14, "%e", -1.0e+1);
+  EQL (12, 13, "%e",  1.0e+12);
+  EQL (13, 14, "%e", -1.0e+12);
+  EQL (13, 14, "%e",  1.0e+123);
+  EQL (14, 15, "%e", -1.0e+123);
+
+  EQL (12, 13, "%e",  9.999e+99);
+  EQL (12, 13, "%e",  9.9999e+99);
+  EQL (12, 13, "%e",  9.99999e+99);
+
+  /* The actual output of the following directive depends on the rounding
+     mode.  */
+  /* EQL (12, "%e",  9.9999994e+99); */
+
+  EQL (12, 13, "%e",  1.0e-1);
+  EQL (12, 13, "%e",  1.0e-12);
+  EQL (13, 14, "%e",  1.0e-123);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_e_long_double (void)
+{
+  EQL (12, 13, "%Le",  1.0e0L);
+  EQL (13, 14, "%Le", -1.0e0L);
+  EQL (12, 13, "%Le",  1.0e+1L);
+  EQL (13, 14, "%Le", -1.0e+1L);
+  EQL (12, 13, "%Le",  1.0e+12L);
+  EQL (13, 14, "%Le", -1.0e+12L);
+  EQL (13, 14, "%Le",  1.0e+123L);
+  EQL (14, 15, "%Le", -1.0e+123L);
+
+  EQL (12, 13, "%Le",  9.999e+99L);
+  EQL (12, 13, "%Le",  9.9999e+99L);
+  EQL (12, 13, "%Le",  9.99999e+99L);
+  EQL (12, 13, "%Le",  9.999999e+99L);
+
+  /* The actual output of the following directive depends on the rounding
+     mode.  */
+  /* EQL (12, "%Le",  9.9999994e+99L); */
+
+  EQL (12, 13, "%Le",  1.0e-1L);
+  EQL (12, 13, "%Le",  1.0e-12L);
+  EQL (13, 14, "%Le",  1.0e-123L);
+
+  EQL ( 6,  7, "%.0Le",   1.0e-111L);
+  EQL ( 8,  9, "%.1Le",   1.0e-111L);
+  EQL (19, 20, "%.12Le",  1.0e-112L);
+  EQL (20, 21, "%.13Le",  1.0e-113L);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_f_double (void)
+{
+  EQL (  8,   9, "%f", 0.0e0);
+  EQL (  8,   9, "%f", 0.1e0);
+  EQL (  8,   9, "%f", 0.12e0);
+  EQL (  8,   9, "%f", 0.123e0);
+  EQL (  8,   9, "%f", 0.1234e0);
+  EQL (  8,   9, "%f", 0.12345e0);
+  EQL (  8,   9, "%f", 0.123456e0);
+  EQL (  8,   9, "%f", 1.234567e0);
+
+  EQL (  9,  10, "%f", 1.0e+1);
+  EQL ( 20,  21, "%f", 1.0e+12);
+  EQL (130, 131, "%f", 1.0e+123);
+
+  EQL (  8,   9, "%f", 1.0e-1);
+  EQL (  8,   9, "%f", 1.0e-12);
+  EQL (  8,   9, "%f", 1.0e-123);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_f_long_double (void)
+{
+  EQL (  8,   9, "%Lf", 0.0e0L);
+  EQL (  8,   9, "%Lf", 0.1e0L);
+  EQL (  8,   9, "%Lf", 0.12e0L);
+  EQL (  8,   9, "%Lf", 0.123e0L);
+  EQL (  8,   9, "%Lf", 0.1234e0L);
+  EQL (  8,   9, "%Lf", 0.12345e0L);
+  EQL (  8,   9, "%Lf", 0.123456e0L);
+  EQL (  8,   9, "%Lf", 1.234567e0L);
+
+  EQL (  9,  10, "%Lf", 1.0e+1L);
+  EQL ( 20,  21, "%Lf", 1.0e+12L);
+  EQL (130, 131, "%Lf", 1.0e+123L);
+
+  EQL (  8,   9, "%Lf", 1.0e-1L);
+  EQL (  8,   9, "%Lf", 1.0e-12L);
+  EQL (  8,   9, "%Lf", 1.0e-123L);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_s (int i)
+{
+  EQL (  0,   1, "%s", "");
+  EQL (  0,   1, "%s", "\0");
+  EQL (  1,   2, "%1s", "");
+  EQL (  1,   2, "%s", "1");
+  EQL (  2,   3, "%2s", "");
+  EQL (  2,   3, "%s", "12");
+  EQL (  2,   3, "%s%s", "12", "");
+  EQL (  2,   3, "%s%s", "", "12");
+  EQL (  2,   3, "%s%s", "1", "2");
+  EQL (  3,   4, "%3s", "");
+  EQL (  3,   4, "%3s", "1");
+  EQL (  3,   4, "%3s", "12");
+  EQL (  3,   4, "%3s", "123");
+  EQL (  3,   4, "%3.3s", "1");
+  EQL (  3,   4, "%3.3s", "12");
+  EQL (  3,   4, "%3.3s", "123");
+  EQL (  3,   4, "%3.3s", "1234");
+  EQL (  3,   4, "%3.3s", "12345");
+  EQL (  3,   4, "%s %s", "1", "2");
+  EQL (  4,   5, "%s %s", "12", "3");
+  EQL (  5,   6, "%s %s", "12", "34");
+  EQL (  5,   6, "[%s %s]", "1", "2");
+  EQL (  6,   7, "[%s %s]", "12", "3");
+  EQL (  7,   8, "[%s %s]", "12", "34");
+
+  /* Verify the result of a conditional expression involving string
+     literals is in the expected range of their lengths.  */
+  RNG (  0,   3,   4, "%-s", i ? ""    : "123");
+  RNG (  1,   4,   5, "%-s", i ? "1"   : "1234");
+  RNG (  2,   5,   6, "%-s", i ? "12"  : "12345");
+  RNG (  3,   6,   7, "%-s", i ? "123" : "123456");
+}
+
+int main (void)
+{
+  test_c ('?');
+  test_d_i (0xdeadbeef, 0xdeadbeefL);
+  test_x ('?', 0xdead, 0xdeadbeef);
+
+  test_a_double ();
+  test_e_double ();
+  test_f_double ();
+
+  test_a_long_double ();
+  test_e_long_double ();
+  test_f_long_double ();
+
+  test_s (0);
+
+  if (nfails)
+    {
+      __builtin_printf ("%u out of %u tests failed\n", nfails, ntests);
+      __builtin_abort ();
+    }
+
+  return 0;
+}
index 251b30ffcdffb21a466cfed8eb2507dbda4f2a4b..5903fde0bb5050e73d74b5346915c0e4fcb5968a 100644 (file)
@@ -471,6 +471,7 @@ extern simple_ipa_opt_pass *make_pass_ipa_oacc (gcc::context *ctxt);
 extern simple_ipa_opt_pass *make_pass_ipa_oacc_kernels (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_gen_hsail (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_warn_nonnull_compare (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_sprintf_length (gcc::context *ctxt);
 
 /* IPA Passes */
 extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);