]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/gimple-ssa-sprintf.c
[Ada] Replace low-level membership tests with high-level routines
[thirdparty/gcc.git] / gcc / gimple-ssa-sprintf.c
index 7688439aab70d299708ad656af02dbbc9fda2b23..88ba1f2cac1690aa77de8cee3c41432bd68200dc 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2016-2017 Free Software Foundation, Inc.
+/* Copyright (C) 2016-2019 Free Software Foundation, Inc.
    Contributed by Martin Sebor <msebor@redhat.com>.
 
 This file is part of GCC.
@@ -65,8 +65,12 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-ssa-propagate.h"
 #include "calls.h"
 #include "cfgloop.h"
+#include "tree-scalar-evolution.h"
+#include "tree-ssa-loop.h"
 #include "intl.h"
+#include "langhooks.h"
 
+#include "attribs.h"
 #include "builtins.h"
 #include "stor-layout.h"
 
@@ -78,6 +82,10 @@ along with GCC; see the file COPYING3.  If not see
 #include "toplev.h"
 #include "substring-locations.h"
 #include "diagnostic.h"
+#include "domwalk.h"
+#include "alloc-pool.h"
+#include "vr-values.h"
+#include "gimple-ssa-evrp-analyze.h"
 
 /* The likely worst case value of MB_LEN_MAX for the target, large enough
    for UTF-8.  Ideally, this would be obtained by a target hook if it were
@@ -112,6 +120,23 @@ static int warn_level;
 
 struct format_result;
 
+class sprintf_dom_walker : public dom_walker
+{
+ public:
+  sprintf_dom_walker ()
+    : dom_walker (CDI_DOMINATORS),
+      evrp_range_analyzer (false) {}
+  ~sprintf_dom_walker () {}
+
+  edge before_dom_children (basic_block) FINAL OVERRIDE;
+  void after_dom_children (basic_block) FINAL OVERRIDE;
+  bool handle_gimple_call (gimple_stmt_iterator *);
+
+  struct call_info;
+  bool compute_format_length (call_info &, format_result *);
+  class evrp_range_analyzer evrp_range_analyzer;
+};
+
 class pass_sprintf_length : public gimple_opt_pass
 {
   bool fold_return_value;
@@ -134,10 +159,6 @@ public:
       fold_return_value = param;
     }
 
-  bool handle_gimple_call (gimple_stmt_iterator *);
-
-  struct call_info;
-  bool compute_format_length (call_info &, format_result *);
 };
 
 bool
@@ -195,12 +216,14 @@ struct format_result
      the return value optimization.  */
   bool knownrange;
 
-  /* True if no individual directive resulted in more than 4095 bytes
-     of output (the total NUMBER_CHARS_{MIN,MAX} might be greater).
-     Implementations are not required to handle directives that produce
-     more than 4K bytes (leading to undefined behavior) and so when one
-     is found it disables the return value optimization.  */
-  bool under4k;
+  /* True if no individual directive could fail or result in more than
+     4095 bytes of output (the total NUMBER_CHARS_{MIN,MAX} might be
+     greater).  Implementations are not required to handle directives
+     that produce more than 4K bytes (leading to undefined behavior)
+     and so when one is found it disables the return value optimization.
+     Similarly, directives that can fail (such as wide character
+     directives) disable the optimization.  */
+  bool posunder4k;
 
   /* True when a floating point directive has been seen in the format
      string.  */
@@ -273,162 +296,210 @@ target_size_max ()
   return tree_to_uhwi (TYPE_MAX_VALUE (size_type_node));
 }
 
-/* Return the constant initial value of DECL if available or DECL
-   otherwise.  Same as the synonymous function in c/c-typeck.c.  */
+/* A straightforward mapping from the execution character set to the host
+   character set indexed by execution character.  */
 
-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;
-}
+static char target_to_host_charmap[256];
 
-/* Given FORMAT, set *PLOC to the source location of the format string
-   and return the format string if it is known or null otherwise.  */
+/* Initialize a mapping from the execution character set to the host
+   character set.  */
 
-static const char*
-get_format_string (tree format, location_t *ploc)
+static bool
+init_target_to_host_charmap ()
 {
-  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;
-    }
+  /* If the percent sign is non-zero the mapping has already been
+     initialized.  */
+  if (target_to_host_charmap['%'])
+    return true;
 
-  HOST_WIDE_INT offset = 0;
+  /* Initialize the target_percent character (done elsewhere).  */
+  if (!init_target_chars ())
+    return false;
 
-  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);
+  /* The subset of the source character set used by printf conversion
+     specifications (strictly speaking, not all letters are used but
+     they are included here for the sake of simplicity).  The dollar
+     sign must be included even though it's not in the basic source
+     character set.  */
+  const char srcset[] = " 0123456789!\"#%&'()*+,-./:;<=>?[\\]^_{|}~$"
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
-      if (TREE_CODE (arg1) != INTEGER_CST)
-       return NULL;
+  /* Set the mapping for all characters to some ordinary value (i,e.,
+     not none used in printf conversion specifications) and overwrite
+     those that are used by conversion specifications with their
+     corresponding values.  */
+  memset (target_to_host_charmap + 1, '?', sizeof target_to_host_charmap - 1);
 
-      format = arg0;
+  /* Are the two sets of characters the same?  */
+  bool all_same_p = true;
 
-      /* POINTER_PLUS_EXPR offsets are to be interpreted signed.  */
-      if (!cst_and_fits_in_hwi (arg1))
-       return NULL;
+  for (const char *pc = srcset; *pc; ++pc)
+    {
+      /* Slice off the high end bits in case target characters are
+        signed.  All values are expected to be non-nul, otherwise
+        there's a problem.  */
+      if (unsigned char tc = lang_hooks.to_target_charset (*pc))
+       {
+         target_to_host_charmap[tc] = *pc;
+         if (tc != *pc)
+           all_same_p = false;
+       }
+      else
+       return false;
 
-      offset = int_cst_value (arg1);
     }
 
-  if (TREE_CODE (format) != ADDR_EXPR)
-    return NULL;
-
-  *ploc = EXPR_LOC_OR_LOC (format, input_location);
+  /* Set the first element to a non-zero value if the mapping
+     is 1-to-1, otherwise leave it clear (NUL is assumed to be
+     the same in both character sets).  */
+  target_to_host_charmap[0] = all_same_p;
 
-  format = TREE_OPERAND (format, 0);
+  return true;
+}
 
-  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);
+/* Return the host source character corresponding to the character
+   CH in the execution character set if one exists, or some innocuous
+   (non-special, non-nul) source character otherwise.  */
 
-  if (offset < 0)
-    return NULL;
+static inline unsigned char
+target_to_host (unsigned char ch)
+{
+  return target_to_host_charmap[ch];
+}
 
-  tree array_init;
-  tree array_size = NULL_TREE;
+/* Convert an initial substring of the string TARGSTR consisting of
+   characters in the execution character set into a string in the
+   source character set on the host and store up to HOSTSZ characters
+   in the buffer pointed to by HOSTR.  Return HOSTR.  */
 
-  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;
+static const char*
+target_to_host (char *hostr, size_t hostsz, const char *targstr)
+{
+  /* Make sure the buffer is reasonably big.  */
+  gcc_assert (hostsz > 4);
 
-  if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format))) != char_type_node)
+  /* The interesting subset of source and execution characters are
+     the same so no conversion is necessary.  However, truncate
+     overlong strings just like the translated strings are.  */
+  if (target_to_host_charmap['\0'] == 1)
     {
-      /* Wide format string.  */
-      return NULL;
+      size_t len = strlen (targstr);
+      if (len >= hostsz)
+       {
+         memcpy (hostr, targstr, hostsz - 4);
+         strcpy (hostr + hostsz - 4, "...");
+       }
+      else
+       memcpy (hostr, targstr, len + 1);
+      return hostr;
     }
 
-  const char *fmtstr = TREE_STRING_POINTER (format);
-  unsigned fmtlen = TREE_STRING_LENGTH (format);
-
-  if (array_size)
+  /* Convert the initial substring of TARGSTR to the corresponding
+     characters in the host set, appending "..." if TARGSTR is too
+     long to fit.  Using the static buffer assumes the function is
+     not called in between sequence points (which it isn't).  */
+  for (char *ph = hostr; ; ++targstr)
     {
-      /* Variable length arrays can't be initialized.  */
-      gcc_assert (TREE_CODE (array_size) == INTEGER_CST);
+      *ph++ = target_to_host (*targstr);
+      if (!*targstr)
+       break;
 
-      if (tree_fits_shwi_p (array_size))
+      if (size_t (ph - hostr) == hostsz)
        {
-         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;
+         strcpy (ph - 4, "...");
+         break;
        }
     }
-  if (offset)
-    {
-      if (offset >= fmtlen)
-       return NULL;
 
-      fmtstr += offset;
-      fmtlen -= offset;
-    }
+  return hostr;
+}
+
+/* Convert the sequence of decimal digits in the execution character
+   starting at *PS to a HOST_WIDE_INT, analogously to strtol.  Return
+   the result and set *PS to one past the last converted character.
+   On range error set ERANGE to the digit that caused it.  */
 
-  if (fmtlen < 1 || fmtstr[--fmtlen] != 0)
+static inline HOST_WIDE_INT
+target_strtowi (const char **ps, const char **erange)
+{
+  unsigned HOST_WIDE_INT val = 0;
+  for ( ; ; ++*ps)
     {
-      /* 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;
+      unsigned char c = target_to_host (**ps);
+      if (ISDIGIT (c))
+       {
+         c -= '0';
+
+         /* Check for overflow.  */
+         if (val > ((unsigned HOST_WIDE_INT) HOST_WIDE_INT_MAX - c) / 10LU)
+           {
+             val = HOST_WIDE_INT_MAX;
+             *erange = *ps;
+
+             /* Skip the remaining digits.  */
+             do
+               c = target_to_host (*++*ps);
+             while (ISDIGIT (c));
+             break;
+           }
+         else
+           val = val * 10 + c;
+       }
+      else
+       break;
     }
 
-  return fmtstr;
+  return val;
 }
 
-/* The format_warning_at_substring function is not used here in a way
-   that makes using attribute format viable.  Suppress the warning.  */
+/* Given FORMAT, set *PLOC to the source location of the format string
+   and return the format string if it is known or null otherwise.  */
 
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
+static const char*
+get_format_string (tree format, location_t *ploc)
+{
+  *ploc = EXPR_LOC_OR_LOC (format, input_location);
+
+  return c_getstr (format);
+}
+
+/* For convenience and brevity, shorter named entrypoints of
+   format_string_diagnostic_t::emit_warning_va and
+   format_string_diagnostic_t::emit_warning_n_va.
+   These have to be functions with the attribute so that exgettext
+   works properly.  */
 
-/* For convenience and brevity.  */
+static bool
+ATTRIBUTE_GCC_DIAG (5, 6)
+fmtwarn (const substring_loc &fmt_loc, location_t param_loc,
+        const char *corrected_substring, int opt, const char *gmsgid, ...)
+{
+  format_string_diagnostic_t diag (fmt_loc, NULL, param_loc, NULL,
+                                  corrected_substring);
+  va_list ap;
+  va_start (ap, gmsgid);
+  bool warned = diag.emit_warning_va (opt, gmsgid, &ap);
+  va_end (ap);
+
+  return warned;
+}
 
 static bool
-  (* const fmtwarn) (const substring_loc &, const source_range *,
-                    const char *, int, const char *, ...)
-  = format_warning_at_substring;
+ATTRIBUTE_GCC_DIAG (6, 8) ATTRIBUTE_GCC_DIAG (7, 8)
+fmtwarn_n (const substring_loc &fmt_loc, location_t param_loc,
+          const char *corrected_substring, int opt, unsigned HOST_WIDE_INT n,
+          const char *singular_gmsgid, const char *plural_gmsgid, ...)
+{
+  format_string_diagnostic_t diag (fmt_loc, NULL, param_loc, NULL,
+                                  corrected_substring);
+  va_list ap;
+  va_start (ap, plural_gmsgid);
+  bool warned = diag.emit_warning_n_va (opt, n, singular_gmsgid, plural_gmsgid,
+                                       &ap);
+  va_end (ap);
+
+  return warned;
+}
 
 /* Format length modifiers.  */
 
@@ -449,14 +520,15 @@ enum format_lengths
 /* Description of the result of conversion either of a single directive
    or the whole format string.  */
 
-struct fmtresult
+class fmtresult
 {
+public:
   /* Construct a FMTRESULT object with all counters initialized
      to MIN.  KNOWNRANGE is set when MIN is valid.  */
   fmtresult (unsigned HOST_WIDE_INT min = HOST_WIDE_INT_MAX)
-  : argmin (), argmax (),
+  : argmin (), argmax (), nonstr (),
     knownrange (min < HOST_WIDE_INT_MAX),
-    nullp ()
+    mayfail (), nullp ()
   {
     range.min = min;
     range.max = min;
@@ -468,9 +540,9 @@ struct fmtresult
      KNOWNRANGE is set when both MIN and MAX are valid.   */
   fmtresult (unsigned HOST_WIDE_INT min, unsigned HOST_WIDE_INT max,
             unsigned HOST_WIDE_INT likely = HOST_WIDE_INT_MAX)
-  : argmin (), argmax (),
+  : argmin (), argmax (), nonstr (),
     knownrange (min < HOST_WIDE_INT_MAX && max < HOST_WIDE_INT_MAX),
-    nullp ()
+    mayfail (), nullp ()
   {
     range.min = min;
     range.max = max;
@@ -495,11 +567,19 @@ struct fmtresult
      results in on output for an argument in the range above.  */
   result_range range;
 
+  /* Non-nul when the argument of a string directive is not a nul
+     terminated string.  */
+  tree nonstr;
+
   /* True when the range above is obtained from a known value of
      a directive's argument or its bounds and not the result of
      heuristics that depend on warning levels.  */
   bool knownrange;
 
+  /* True for a directive that may fail (such as wide character
+     directives).  */
+  bool mayfail;
+
   /* True when the argument is a null pointer.  */
   bool nullp;
 };
@@ -587,20 +667,24 @@ unsigned
 fmtresult::type_max_digits (tree type, int base)
 {
   unsigned prec = TYPE_PRECISION (type);
-  if (base == 8)
-    return (prec + 2) / 3;
-
-  if (base == 16)
-    return prec / 4;
+  switch (base)
+    {
+    case 8:
+      return (prec + 2) / 3;
+    case 10:
+      /* Decimal approximation: yields 3, 5, 10, and 20 for precision
+        of 8, 16, 32, and 64 bits.  */
+      return prec * 301 / 1000 + 1;
+    case 16:
+      return prec / 4;
+    }
 
-  /* Decimal approximation: yields 3, 5, 10, and 20 for precision
-     of 8, 16, 32, and 64 bits.  */
-  return prec * 301 / 1000 + 1;
+  gcc_unreachable ();
 }
 
 static bool
-get_int_range (tree, tree, HOST_WIDE_INT *, HOST_WIDE_INT *,
-              bool, HOST_WIDE_INT);
+get_int_range (tree, HOST_WIDE_INT *, HOST_WIDE_INT *, bool, HOST_WIDE_INT,
+              class vr_values *vr_values);
 
 /* Description of a format directive.  A directive is either a plain
    string or a conversion specification that starts with '%'.  */
@@ -635,7 +719,7 @@ struct directive
 
   /* Format conversion function that given a directive and an argument
      returns the formatting result.  */
-  fmtresult (*fmtfunc) (const directive &, tree);
+  fmtresult (*fmtfunc) (const directive &, tree, vr_values *);
 
   /* Return True when a the format flag CHR has been used.  */
   bool get_flag (char chr) const
@@ -672,9 +756,9 @@ struct directive
      or 0, whichever is greater.  For a non-constant ARG in some range
      set width to its range adjusting each bound to -1 if it's less.
      For an indeterminate ARG set width to [0, INT_MAX].  */
-  void set_width (tree arg)
+  void set_width (tree arg, vr_values *vr_values)
   {
-    get_int_range (arg, integer_type_node, width, width + 1, true, 0);
+    get_int_range (arg, width, width + 1, true, 0, vr_values);
   }
 
   /* Set both bounds of the precision range to VAL.  */
@@ -688,9 +772,19 @@ struct directive
      or -1 whichever is greater.  For a non-constant ARG in some range
      set precision to its range adjusting each bound to -1 if it's less.
      For an indeterminate ARG set precision to [-1, INT_MAX].  */
-  void set_precision (tree arg)
+  void set_precision (tree arg, vr_values *vr_values)
+  {
+    get_int_range (arg, prec, prec + 1, false, -1, vr_values);
+  }
+
+  /* Return true if both width and precision are known to be
+     either constant or in some range, false otherwise.  */
+  bool known_width_and_precision () const
   {
-    get_int_range (arg, integer_type_node, prec, prec + 1, false, -1);
+    return ((width[1] < 0
+            || (unsigned HOST_WIDE_INT)width[1] <= target_int_max ())
+           && (prec[1] < 0
+               || (unsigned HOST_WIDE_INT)prec[1] < target_int_max ()));
   }
 };
 
@@ -810,7 +904,7 @@ bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
 
 /* Description of a call to a formatted function.  */
 
-struct pass_sprintf_length::call_info
+struct sprintf_dom_walker::call_info
 {
   /* Function call statement.  */
   gimple *callstmt;
@@ -856,12 +950,35 @@ struct pass_sprintf_length::call_info
   {
     return bounded ? OPT_Wformat_truncation_ : OPT_Wformat_overflow_;
   }
+
+  /* Return true for calls to file formatted functions.  */
+  bool is_file_func () const
+  {
+    return (fncode == BUILT_IN_FPRINTF
+           || fncode == BUILT_IN_FPRINTF_CHK
+           || fncode == BUILT_IN_FPRINTF_UNLOCKED
+           || fncode == BUILT_IN_VFPRINTF
+           || fncode == BUILT_IN_VFPRINTF_CHK);
+  }
+
+  /* Return true for calls to string formatted functions.  */
+  bool is_string_func () const
+  {
+    return (fncode == BUILT_IN_SPRINTF
+           || fncode == BUILT_IN_SPRINTF_CHK
+           || fncode == BUILT_IN_SNPRINTF
+           || fncode == BUILT_IN_SNPRINTF_CHK
+           || fncode == BUILT_IN_VSPRINTF
+           || fncode == BUILT_IN_VSPRINTF_CHK
+           || fncode == BUILT_IN_VSNPRINTF
+           || fncode == BUILT_IN_VSNPRINTF_CHK);
+  }
 };
 
 /* Return the result of formatting a no-op directive (such as '%n').  */
 
 static fmtresult
-format_none (const directive &, tree)
+format_none (const directive &, tree, vr_values *)
 {
   fmtresult res (0);
   return res;
@@ -870,7 +987,7 @@ format_none (const directive &, tree)
 /* Return the result of formatting the '%%' directive.  */
 
 static fmtresult
-format_percent (const directive &, tree)
+format_percent (const directive &, tree, vr_values *)
 {
   fmtresult res (1);
   return res;
@@ -903,10 +1020,12 @@ build_intmax_type_nodes (tree *pintmax, tree *puintmax)
       for (int i = 0; i < NUM_INT_N_ENTS; i++)
        if (int_n_enabled_p[i])
          {
-           char name[50];
+           char name[50], altname[50];
            sprintf (name, "__int%d unsigned", int_n_data[i].bitsize);
+           sprintf (altname, "__int%d__ unsigned", int_n_data[i].bitsize);
 
-           if (strcmp (name, UINTMAX_TYPE) == 0)
+           if (strcmp (name, UINTMAX_TYPE) == 0
+               || strcmp (altname, UINTMAX_TYPE) == 0)
              {
                *pintmax = int_n_trees[i].signed_type;
                *puintmax = int_n_trees[i].unsigned_type;
@@ -917,27 +1036,31 @@ build_intmax_type_nodes (tree *pintmax, tree *puintmax)
     }
 }
 
-/* Determine the range [*PMIN, *PMAX] that the expression ARG of TYPE
-   is in.  Return true when the range is a subrange of that of TYPE.
-   Whn ARG is null it is as if it had the full range of TYPE.
+/* Determine the range [*PMIN, *PMAX] that the expression ARG is
+   in and that is representable in type int.
+   Return true when the range is a subrange of that of int.
+   When ARG is null it is as if it had the full range of int.
    When ABSOLUTE is true the range reflects the absolute value of
    the argument.  When ABSOLUTE is false, negative bounds of
    the determined range are replaced with NEGBOUND.  */
 
 static bool
-get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
-              bool absolute, HOST_WIDE_INT negbound)
+get_int_range (tree arg, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
+              bool absolute, HOST_WIDE_INT negbound,
+              class vr_values *vr_values)
 {
+  /* The type of the result.  */
+  const_tree type = integer_type_node;
+
   bool knownrange = false;
 
   if (!arg)
     {
-      *pmin = (TYPE_UNSIGNED (type)
-              ? tree_to_uhwi (TYPE_MIN_VALUE (type))
-              : tree_to_shwi (TYPE_MIN_VALUE (type)));
-      *pmax = tree_to_uhwi (TYPE_MAX_VALUE (type));
+      *pmin = tree_to_shwi (TYPE_MIN_VALUE (type));
+      *pmax = tree_to_shwi (TYPE_MAX_VALUE (type));
     }
-  else if (TREE_CODE (arg) == INTEGER_CST)
+  else if (TREE_CODE (arg) == INTEGER_CST
+          && TYPE_PRECISION (TREE_TYPE (arg)) <= TYPE_PRECISION (type))
     {
       /* For a constant argument return its value adjusted as specified
         by NEGATIVE and NEGBOUND and return true to indicate that the
@@ -951,38 +1074,51 @@ get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
       /* True if the argument's range cannot be determined.  */
       bool unknown = true;
 
-      type = TREE_TYPE (arg);
+      tree argtype = TREE_TYPE (arg);
 
+      /* Ignore invalid arguments with greater precision that that
+        of the expected type (e.g., in sprintf("%*i", 12LL, i)).
+        They will have been detected and diagnosed by -Wformat and
+        so it's not important to complicate this code to try to deal
+        with them again.  */
       if (TREE_CODE (arg) == SSA_NAME
-         && TREE_CODE (type) == INTEGER_TYPE)
+         && INTEGRAL_TYPE_P (argtype)
+         && TYPE_PRECISION (argtype) <= TYPE_PRECISION (type))
        {
          /* Try to determine the range of values of the integer argument.  */
-         wide_int min, max;
-         enum value_range_type range_type = get_range_info (arg, &min, &max);
-         if (range_type == VR_RANGE)
+         const value_range *vr = vr_values->get_value_range (arg);
+         if (range_int_cst_p (vr))
            {
              HOST_WIDE_INT type_min
-               = (TYPE_UNSIGNED (type)
-                  ? tree_to_uhwi (TYPE_MIN_VALUE (type))
-                  : tree_to_shwi (TYPE_MIN_VALUE (type)));
-
-             HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (type));
+               = (TYPE_UNSIGNED (argtype)
+                  ? tree_to_uhwi (TYPE_MIN_VALUE (argtype))
+                  : tree_to_shwi (TYPE_MIN_VALUE (argtype)));
 
-             *pmin = min.to_shwi ();
-             *pmax = max.to_shwi ();
+             HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (argtype));
 
-             /* Return true if the adjusted range is a subrange of
-                the full range of the argument's type.  */
-             knownrange = type_min < *pmin || *pmax < type_max;
+             *pmin = TREE_INT_CST_LOW (vr->min ());
+             *pmax = TREE_INT_CST_LOW (vr->max ());
 
-             unknown = false;
+             if (*pmin < *pmax)
+               {
+                 /* Return true if the adjusted range is a subrange of
+                    the full range of the argument's type.  *PMAX may
+                    be less than *PMIN when the argument is unsigned
+                    and its upper bound is in excess of TYPE_MAX.  In
+                    that (invalid) case disregard the range and use that
+                    of the expected type instead.  */
+                 knownrange = type_min < *pmin || *pmax < type_max;
+
+                 unknown = false;
+               }
            }
        }
 
       /* Handle an argument with an unknown range as if none had been
         provided.  */
       if (unknown)
-       return get_int_range (NULL_TREE, type, pmin, pmax, absolute, negbound);
+       return get_int_range (NULL_TREE, pmin, pmax, absolute,
+                             negbound, vr_values);
     }
 
   /* Adjust each bound as specified by ABSOLUTE and NEGBOUND.  */
@@ -994,6 +1130,9 @@ get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
            *pmin = *pmax = -*pmin;
          else
            {
+             /* Make sure signed overlow is avoided.  */
+             gcc_assert (*pmin != HOST_WIDE_INT_MIN);
+
              HOST_WIDE_INT tmp = -*pmin;
              *pmin = 0;
              if (*pmax < tmp)
@@ -1064,7 +1203,7 @@ adjust_range_for_overflow (tree dirtype, tree *argmin, tree *argmax)
    used when the directive argument or its value isn't known.  */
 
 static fmtresult
-format_integer (const directive &dir, tree arg)
+format_integer (const directive &dir, tree arg, vr_values *vr_values)
 {
   tree intmax_type_node;
   tree uintmax_type_node;
@@ -1180,10 +1319,10 @@ format_integer (const directive &dir, tree arg)
          /* As a special case, a precision of zero with a zero argument
             results in zero bytes except in base 8 when the '#' flag is
             specified, and for signed conversions in base 8 and 10 when
-            flags when either the space or '+' flag has been specified
-            when it results in just one byte (with width having the normal
-            effect).  This must extend to the case of a specified precision
-            with an unknown value because it can be zero.  */
+            either the space or '+' flag has been specified and it results
+            in just one byte (with width having the normal effect).  This
+            must extend to the case of a specified precision with
+            an unknown value because it can be zero.  */
          res.range.min = ((base == 8 && dir.get_flag ('#')) || maybesign);
          if (res.range.min == 0 && dir.prec[0] != dir.prec[1])
            {
@@ -1209,6 +1348,7 @@ format_integer (const directive &dir, tree arg)
            res.range.max = tree_digits (arg, base, dir.prec[1],
                                         maybesign, maybebase);
          res.range.likely = res.range.min;
+         res.knownrange = true;
        }
 
       res.range.unlikely = res.range.max;
@@ -1222,7 +1362,7 @@ format_integer (const directive &dir, tree arg)
 
       return res;
     }
-  else if (TREE_CODE (TREE_TYPE (arg)) == INTEGER_TYPE
+  else if (INTEGRAL_TYPE_P (TREE_TYPE (arg))
           || TREE_CODE (TREE_TYPE (arg)) == POINTER_TYPE)
     /* Determine the type of the provided non-constant argument.  */
     argtype = TREE_TYPE (arg);
@@ -1242,31 +1382,32 @@ format_integer (const directive &dir, tree arg)
 
   if (arg
       && TREE_CODE (arg) == SSA_NAME
-      && TREE_CODE (argtype) == INTEGER_TYPE)
+      && INTEGRAL_TYPE_P (argtype))
     {
       /* 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)
+      const value_range *vr = vr_values->get_value_range (arg);
+      if (range_int_cst_p (vr))
        {
-         argmin = wide_int_to_tree (argtype, min);
-         argmax = wide_int_to_tree (argtype, max);
+         argmin = vr->min ();
+         argmax = vr->max ();
 
          /* Set KNOWNRANGE if the argument is in a known subrange
-            of the directive's type (KNOWNRANGE may be reset below).  */
+            of the directive's type and neither width nor precision
+            is unknown.  (KNOWNRANGE may be reset below).  */
          res.knownrange
-           = (!tree_int_cst_equal (TYPE_MIN_VALUE (dirtype), argmin)
-              || !tree_int_cst_equal (TYPE_MAX_VALUE (dirtype), argmax));
+           = ((!tree_int_cst_equal (TYPE_MIN_VALUE (dirtype), argmin)
+               || !tree_int_cst_equal (TYPE_MAX_VALUE (dirtype), argmax))
+              && dir.known_width_and_precision ());
 
          res.argmin = argmin;
          res.argmax = argmax;
        }
-      else if (range_type == VR_ANTI_RANGE)
+      else if (vr->kind () == VR_ANTI_RANGE)
        {
          /* Handle anti-ranges if/when bug 71690 is resolved.  */
        }
-      else if (range_type == VR_VARYING)
+      else if (vr->varying_p () || vr->undefined_p ())
        {
          /* The argument here may be the result of promoting the actual
             argument to int.  Try to determine the type of the actual
@@ -1279,13 +1420,13 @@ format_integer (const directive &dir, tree arg)
              if (code == INTEGER_CST)
                {
                  arg = gimple_assign_rhs1 (def);
-                 return format_integer (dir, arg);
+                 return format_integer (dir, arg, vr_values);
                }
 
              if (code == NOP_EXPR)
                {
                  tree type = TREE_TYPE (gimple_assign_rhs1 (def));
-                 if (TREE_CODE (type) == INTEGER_TYPE
+                 if (INTEGRAL_TYPE_P (type)
                      || TREE_CODE (type) == POINTER_TYPE)
                    argtype = type;
                }
@@ -1324,16 +1465,16 @@ format_integer (const directive &dir, tree arg)
       /* For unsigned conversions/directives or signed when
         the minimum is positive, use the minimum and maximum to compute
         the shortest and longest output, respectively.  */
-      res.range.min = format_integer (dir, argmin).range.min;
-      res.range.max = format_integer (dir, argmax).range.max;
+      res.range.min = format_integer (dir, argmin, vr_values).range.min;
+      res.range.max = format_integer (dir, argmax, vr_values).range.max;
     }
   else if (tree_int_cst_sgn (argmax) < 0)
     {
       /* For signed conversions/directives if maximum is negative,
         use the minimum as the longest output and maximum as the
         shortest output.  */
-      res.range.min = format_integer (dir, argmax).range.min;
-      res.range.max = format_integer (dir, argmin).range.max;
+      res.range.min = format_integer (dir, argmax, vr_values).range.min;
+      res.range.max = format_integer (dir, argmin, vr_values).range.max;
     }
   else
     {
@@ -1341,9 +1482,12 @@ format_integer (const directive &dir, tree arg)
         as the shortest output and for the longest output compute the
         length of the output of both minimum and maximum and pick the
         longer.  */
-      unsigned HOST_WIDE_INT max1 = format_integer (dir, argmin).range.max;
-      unsigned HOST_WIDE_INT max2 = format_integer (dir, argmax).range.max;
-      res.range.min = format_integer (dir, integer_zero_node).range.min;
+      unsigned HOST_WIDE_INT max1
+       = format_integer (dir, argmin, vr_values).range.max;
+      unsigned HOST_WIDE_INT max2
+       = format_integer (dir, argmax, vr_values).range.max;
+      res.range.min
+       = format_integer (dir, integer_zero_node, vr_values).range.min;
       res.range.max = MAX (max1, max2);
     }
 
@@ -1421,12 +1565,12 @@ get_mpfr_format_length (mpfr_ptr x, const char *flags, HOST_WIDE_INT prec,
 
   HOST_WIDE_INT p = prec;
 
-  if (spec == 'G')
+  if (spec == 'G' && !strchr (flags, '#'))
     {
-      /* For G/g, precision gives the maximum number of significant
-        digits which is bounded by LDBL_MAX_10_EXP, or, for a 128
-        bit IEEE extended precision, 4932.  Using twice as much
-        here should be more than sufficient for any real format.  */
+      /* For G/g without the pound flag, precision gives the maximum number
+        of significant digits which is bounded by LDBL_MAX_10_EXP, or, for
+        a 128 bit IEEE extended precision, 4932.  Using twice as much here
+        should be more than sufficient for any real format.  */
       if ((IEEE_MAX_10_EXP * 2) < prec)
        prec = IEEE_MAX_10_EXP * 2;
       p = prec;
@@ -1487,11 +1631,13 @@ format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
 }
 
 /* Return a range representing the minimum and maximum number of bytes
-   that the directive DIR will output for any argument.  This function
-   is used when the directive argument or its value isn't known.  */
+   that the directive DIR will output for any argument.  PREC gives
+   the adjusted precision range to account for negative precisions
+   meaning the default 6.  This function is used when the directive
+   argument or its value isn't known.  */
 
 static fmtresult
-format_floating (const directive &dir)
+format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
 {
   tree type;
 
@@ -1520,8 +1666,13 @@ format_floating (const directive &dir)
   /* The minimum output as determined by flags.  It's always at least 1.
      When plus or space are set the output is preceded by either a sign
      or a space.  */
-  int flagmin = (1 /* for the first digit */
-                + (dir.get_flag ('+') | dir.get_flag (' ')));
+  unsigned flagmin = (1 /* for the first digit */
+                     + (dir.get_flag ('+') | dir.get_flag (' ')));
+
+  /* The minimum is 3 for "inf" and "nan" for all specifiers, plus 1
+     for the plus sign/space with the '+' and ' ' flags, respectively,
+     unless reduced below.  */
+  res.range.min = 2 + flagmin;
 
   /* When the pound flag is set the decimal point is included in output
      regardless of precision.  Whether or not a decimal point is included
@@ -1539,20 +1690,18 @@ format_floating (const directive &dir)
        else if (dir.prec[0] > 0)
          minprec = dir.prec[0] + !radix /* decimal point */;
 
-       res.range.min = (2 /* 0x */
-                        + flagmin
-                        + radix
-                        + minprec
-                        + 3 /* p+0 */);
+       res.range.likely = (2 /* 0x */
+                           + flagmin
+                           + radix
+                           + minprec
+                           + 3 /* p+0 */);
 
-       res.range.max = format_floating_max (type, 'a', dir.prec[1]);
-       res.range.likely = res.range.min;
+       res.range.max = format_floating_max (type, 'a', prec[1]);
 
        /* The unlikely maximum accounts for the longest multibyte
           decimal point character.  */
        res.range.unlikely = res.range.max;
-       if (dir.prec[0] != dir.prec[1]
-           || dir.prec[0] == -1 || dir.prec[0] > 0)
+       if (dir.prec[1] > 0)
          res.range.unlikely += target_mb_len_max () - 1;
 
        break;
@@ -1561,24 +1710,18 @@ format_floating (const directive &dir)
     case 'E':
     case 'e':
       {
-       /* The minimum output is "[-+]1.234567e+00" regardless
-          of the value of the actual argument.  */
-       HOST_WIDE_INT minprec = 6 + !radix /* decimal point */;
-       if ((dir.prec[0] < 0 && dir.prec[1] > -1) || dir.prec[0] == 0)
-         minprec = 0;
-       else if (dir.prec[0] > 0)
-         minprec = dir.prec[0] + !radix /* decimal point */;
+       /* Minimum output attributable to precision and, when it's
+          non-zero, decimal point.  */
+       HOST_WIDE_INT minprec = prec[0] ? prec[0] + !radix : 0;
 
-       res.range.min = (flagmin
-                        + radix
-                        + minprec
-                        + 2 /* e+ */ + 2);
-       /* MPFR uses a precision of 16 by default for some reason.
-          Set it to the C default of 6.  */
-       int maxprec = dir.prec[1] < 0 ? 6 : dir.prec[1];
-       res.range.max = format_floating_max (type, 'e', maxprec);
+       /* The likely minimum output is "[-+]1.234567e+00" regardless
+          of the value of the actual argument.  */
+       res.range.likely = (flagmin
+                           + radix
+                           + minprec
+                           + 2 /* e+ */ + 2);
 
-       res.range.likely = res.range.min;
+       res.range.max = format_floating_max (type, 'e', prec[1]);
 
        /* The unlikely maximum accounts for the longest multibyte
           decimal point character.  */
@@ -1593,23 +1736,29 @@ format_floating (const directive &dir)
     case 'F':
     case 'f':
       {
-       /* The lower bound when precision isn't specified is 8 bytes
-          ("1.23456" since precision is taken to be 6).  When precision
-          is zero, the lower bound is 1 byte (e.g., "1").  Otherwise,
-          when precision is greater than zero, then the lower bound
-          is 2 plus precision (plus flags).  */
-       HOST_WIDE_INT minprec = 0;
-       if (dir.prec[0] < 0)
-         minprec = dir.prec[1] < 0 ? 6 + !radix /* decimal point */ : 0;
-       else if (dir.prec[0])
-         minprec = dir.prec[0] + !radix /* decimal point */;
-
-       res.range.min = flagmin + radix + minprec;
+       /* Minimum output attributable to precision and, when it's non-zero,
+          decimal point.  */
+       HOST_WIDE_INT minprec = prec[0] ? prec[0] + !radix : 0;
+
+       /* For finite numbers (i.e., not infinity or NaN) the lower bound
+          when precision isn't specified is 8 bytes ("1.23456" since
+          precision is taken to be 6).  When precision is zero, the lower
+          bound is 1 byte (e.g., "1").  Otherwise, when precision is greater
+          than zero, then the lower bound is 2 plus precision (plus flags).
+          But in all cases, the lower bound is no greater than 3.  */
+       unsigned HOST_WIDE_INT min = flagmin + radix + minprec;
+       if (min < res.range.min)
+         res.range.min = min;
 
        /* Compute the upper bound for -TYPE_MAX.  */
-       res.range.max = format_floating_max (type, 'f', dir.prec[1]);
+       res.range.max = format_floating_max (type, 'f', prec[1]);
 
-       res.range.likely = res.range.min;
+       /* The minimum output with unknown precision is a single byte
+          (e.g., "0") but the more likely output is 3 bytes ("0.0").  */
+       if (dir.prec[0] < 0 && dir.prec[1] > 0)
+         res.range.likely = 3;
+       else
+         res.range.likely = min;
 
        /* The unlikely maximum accounts for the longest multibyte
           decimal point character.  */
@@ -1625,10 +1774,47 @@ format_floating (const directive &dir)
        /* The %g output depends on precision and the exponent of
           the argument.  Since the value of the argument isn't known
           the lower bound on the range of bytes (not counting flags
-          or width) is 1.  */
-       res.range.min = flagmin;
-       res.range.max = format_floating_max (type, 'g', dir.prec[1]);
-       res.range.likely = res.range.max;
+          or width) is 1 plus radix (i.e., either "0" or "0." for
+          "%g" and "%#g", respectively, with a zero argument).  */
+       unsigned HOST_WIDE_INT min = flagmin + radix;
+       if (min < res.range.min)
+         res.range.min = min;
+
+       char spec = 'g';
+       HOST_WIDE_INT maxprec = dir.prec[1];
+       if (radix && maxprec)
+         {
+           /* When the pound flag (radix) is set, trailing zeros aren't
+              trimmed and so the longest output is the same as for %e,
+              except with precision minus 1 (as specified in C11).  */
+           spec = 'e';
+           if (maxprec > 0)
+             --maxprec;
+           else if (maxprec < 0)
+             maxprec = 5;
+         }
+       else
+         maxprec = prec[1];
+
+       res.range.max = format_floating_max (type, spec, maxprec);
+
+       /* The likely output is either the maximum computed above
+          minus 1 (assuming the maximum is positive) when precision
+          is known (or unspecified), or the same minimum as for %e
+          (which is computed for a non-negative argument).  Unlike
+          for the other specifiers above the likely output isn't
+          the minimum because for %g that's 1 which is unlikely.  */
+       if (dir.prec[1] < 0
+           || (unsigned HOST_WIDE_INT)dir.prec[1] < target_int_max ())
+         res.range.likely = res.range.max - 1;
+       else
+         {
+           HOST_WIDE_INT minprec = 6 + !radix /* decimal point */;
+           res.range.likely = (flagmin
+                               + radix
+                               + minprec
+                               + 2 /* e+ */ + 2);
+         }
 
        /* The unlikely maximum accounts for the longest multibyte
           decimal point character.  */
@@ -1650,34 +1836,73 @@ format_floating (const directive &dir)
    ARG.  */
 
 static fmtresult
-format_floating (const directive &dir, tree arg)
+format_floating (const directive &dir, tree arg, vr_values *)
 {
-  if (!arg || TREE_CODE (arg) != REAL_CST)
-    return format_floating (dir);
-
   HOST_WIDE_INT prec[] = { dir.prec[0], dir.prec[1] };
+  tree type = (dir.modifier == FMT_LEN_L || dir.modifier == FMT_LEN_ll
+              ? long_double_type_node : double_type_node);
 
+  /* For an indeterminate precision the lower bound must be assumed
+     to be zero.  */
   if (TOUPPER (dir.specifier) == 'A')
     {
+      /* Get the number of fractional decimal digits needed to represent
+        the argument without a loss of accuracy.  */
+      unsigned fmtprec
+       = REAL_MODE_FORMAT (TYPE_MODE (type))->p;
+
+      /* The precision of the IEEE 754 double format is 53.
+        The precision of all other GCC binary double formats
+        is 56 or less.  */
+      unsigned maxprec = fmtprec <= 56 ? 13 : 15;
+
       /* For %a, leave the minimum precision unspecified to let
         MFPR trim trailing zeros (as it and many other systems
         including Glibc happen to do) and set the maximum
         precision to reflect what it would be with trailing zeros
         present (as Solaris and derived systems do).  */
-      if (prec[0] < 0)
-       prec[0] = -1;
-      if (prec[1] < 0)
+      if (dir.prec[1] < 0)
        {
-          unsigned fmtprec
-           = REAL_MODE_FORMAT (TYPE_MODE (TREE_TYPE (arg)))->p;
-
-              /* The precision of the IEEE 754 double format is 53.
-            The precision of all other GCC binary double formats
-            is 56 or less.  */
-         prec[1] = fmtprec <= 56 ? 13 : 15;
+         /* Both bounds are negative implies that precision has
+            not been specified.  */
+         prec[0] = maxprec;
+         prec[1] = -1;
+       }
+      else if (dir.prec[0] < 0)
+       {
+         /* With a negative lower bound and a non-negative upper
+            bound set the minimum precision to zero and the maximum
+            to the greater of the maximum precision (i.e., with
+            trailing zeros present) and the specified upper bound.  */
+         prec[0] = 0;
+         prec[1] = dir.prec[1] < maxprec ? maxprec : dir.prec[1];
+       }
+    }
+  else if (dir.prec[0] < 0)
+    {
+      if (dir.prec[1] < 0)
+       {
+         /* A precision in a strictly negative range is ignored and
+            the default of 6 is used instead.  */
+         prec[0] = prec[1] = 6;
+       }
+      else
+       {
+         /* For a precision in a partly negative range, the lower bound
+            must be assumed to be zero and the new upper bound is the
+            greater of 6 (the default precision used when the specified
+            precision is negative) and the upper bound of the specified
+            range.  */
+         prec[0] = 0;
+         prec[1] = dir.prec[1] < 6 ? 6 : dir.prec[1];
        }
     }
 
+  if (!arg
+      || TREE_CODE (arg) != REAL_CST
+      || !useless_type_conversion_p (type, TREE_TYPE (arg)))
+    return format_floating (dir, prec);
+
   /* The minimum and maximum number of bytes produced by the directive.  */
   fmtresult res;
 
@@ -1685,6 +1910,39 @@ format_floating (const directive &dir, tree arg)
   const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg);
   const real_format *rfmt = REAL_MODE_FORMAT (TYPE_MODE (TREE_TYPE (arg)));
 
+  if (!real_isfinite (rvp))
+    {
+      /* The format for Infinity and NaN is "[-]inf"/"[-]infinity"
+        and "[-]nan" with the choice being implementation-defined
+        but not locale dependent.  */
+      bool sign = dir.get_flag ('+') || real_isneg (rvp);
+      res.range.min = 3 + sign;
+
+      res.range.likely = res.range.min;
+      res.range.max = res.range.min;
+      /* The unlikely maximum is "[-/+]infinity" or "[-/+][qs]nan".
+        For NaN, the C/POSIX standards specify two formats:
+          "[-/+]nan"
+        and
+          "[-/+]nan(n-char-sequence)"
+        No known printf implementation outputs the latter format but AIX
+        outputs QNaN and SNaN for quiet and signalling NaN, respectively,
+        so the unlikely maximum reflects that.  */
+      res.range.unlikely = sign + (real_isinf (rvp) ? 8 : 4);
+
+      /* The range for infinity and NaN is known unless either width
+        or precision is unknown.  Width has the same effect regardless
+        of whether the argument is finite.  Precision is either ignored
+        (e.g., Glibc) or can have an effect on the short vs long format
+        such as inf/infinity (e.g., Solaris).  */
+      res.knownrange = dir.known_width_and_precision ();
+
+      /* Adjust the range for width but ignore precision.  */
+      res.adjust_for_width_or_precision (dir.width);
+
+      return res;
+    }
+
   char fmtstr [40];
   char *pfmt = fmtstr;
 
@@ -1734,12 +1992,23 @@ format_floating (const directive &dir, tree arg)
       res.range.max = tmp;
     }
 
-  res.knownrange = true;
+  /* The range is known unless either width or precision is unknown.  */
+  res.knownrange = dir.known_width_and_precision ();
+
+  /* For the same floating point constant, unless width or precision
+     is unknown, use the longer output as the likely maximum since
+     with round to nearest either is equally likely.  Otheriwse, when
+     precision is unknown, use the greater of the minimum and 3 as
+     the likely output (for "0.0" since zero precision is unlikely).  */
+  if (res.knownrange)
+    res.range.likely = res.range.max;
+  else if (res.range.min < 3
+          && dir.prec[0] < 0
+          && (unsigned HOST_WIDE_INT)dir.prec[1] == target_int_max ())
+    res.range.likely = 3;
+  else
+    res.range.likely = res.range.min;
 
-  /* For the same floating point constant use the longer output
-     as the likely maximum since with round to nearest either is
-     equally likely.  */
-  res.range.likely = res.range.max;
   res.range.unlikely = res.range.max;
 
   if (res.range.max > 2 && (prec[0] != 0 || prec[1] != 0))
@@ -1761,77 +2030,78 @@ format_floating (const directive &dir, tree arg)
    Used by the format_string function below.  */
 
 static fmtresult
-get_string_length (tree str)
+get_string_length (tree str, unsigned eltsize)
 {
   if (!str)
     return fmtresult ();
 
-  if (tree slen = c_strlen (str, 1))
-    {
-      /* Simply return the length of the string.  */
-      fmtresult res (tree_to_shwi (slen));
-      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];
-  bool flexarray = get_range_strlen (str, lenrange);
-
-  if (lenrange [0] || lenrange [1])
-    {
-      HOST_WIDE_INT min
-       = (tree_fits_uhwi_p (lenrange[0])
-          ? tree_to_uhwi (lenrange[0])
-          : 0);
-
-      HOST_WIDE_INT max
-       = (tree_fits_uhwi_p (lenrange[1])
-          ? tree_to_uhwi (lenrange[1])
-          : HOST_WIDE_INT_M1U);
-
-      /* get_range_strlen() returns the target value of SIZE_MAX for
-        strings of unknown length.  Bump it up to HOST_WIDE_INT_M1U
-        which may be bigger.  */
-      if ((unsigned HOST_WIDE_INT)min == target_size_max ())
-       min = HOST_WIDE_INT_M1U;
-      if ((unsigned HOST_WIDE_INT)max == target_size_max ())
-       max = HOST_WIDE_INT_M1U;
-
-      fmtresult res (min, max);
-
-      /* Set RES.KNOWNRANGE to true if and only if all strings referenced
-        by STR are known to be bounded (though not necessarily by their
-        actual length but perhaps by their maximum possible length).  */
-      if (res.range.max < target_int_max ())
-       {
-         res.knownrange = true;
-         /* When the the length of the longest string is known and not
-            excessive use it as the likely length of the string(s).  */
-         res.range.likely = res.range.max;
-       }
-      else
-       {
-         /* When the upper bound is unknown (it can be zero or excessive)
-            set the likely length to the greater of 1 and the length of
-            the shortest string and reset the lower bound to zero.  */
-         res.range.likely = res.range.min ? res.range.min : warn_level > 1;
-         res.range.min = 0;
-       }
-
-      /* If the range of string length has been estimated from the size
-        of an array at the end of a struct assume that it's longer than
-        the array bound says it is in case it's used as a poor man's
-        flexible array member, such as in struct S { char a[4]; };  */
-      res.range.unlikely = flexarray ? HOST_WIDE_INT_MAX : res.range.max;
+     aren't known to point any such arrays result in LENDATA.MAXLEN
+     set to SIZE_MAX.  */
+  c_strlen_data lendata = { };
+  get_range_strlen (str, &lendata, eltsize);
+
+  /* Return the default result when nothing is known about the string. */
+  if (integer_all_onesp (lendata.maxbound)
+      && integer_all_onesp (lendata.maxlen))
+    return fmtresult ();
 
-      return res;
+  HOST_WIDE_INT min
+    = (tree_fits_uhwi_p (lendata.minlen)
+       ? tree_to_uhwi (lendata.minlen)
+       : 0);
+
+  HOST_WIDE_INT max
+    = (tree_fits_uhwi_p (lendata.maxbound)
+       ? tree_to_uhwi (lendata.maxbound)
+       : HOST_WIDE_INT_M1U);
+
+  const bool unbounded = integer_all_onesp (lendata.maxlen);
+
+  /* Set the max/likely counters to unbounded when a minimum is known
+     but the maximum length isn't bounded.  This implies that STR is
+     a conditional expression involving a string of known length and
+     and an expression of unknown/unbounded length.  */
+  if (min
+      && (unsigned HOST_WIDE_INT)min < HOST_WIDE_INT_M1U
+      && unbounded)
+    max = HOST_WIDE_INT_M1U;
+
+  /* get_range_strlen() returns the target value of SIZE_MAX for
+     strings of unknown length.  Bump it up to HOST_WIDE_INT_M1U
+     which may be bigger.  */
+  if ((unsigned HOST_WIDE_INT)min == target_size_max ())
+    min = HOST_WIDE_INT_M1U;
+  if ((unsigned HOST_WIDE_INT)max == target_size_max ())
+    max = HOST_WIDE_INT_M1U;
+
+  fmtresult res (min, max);
+  res.nonstr = lendata.decl;
+
+  /* Set RES.KNOWNRANGE to true if and only if all strings referenced
+     by STR are known to be bounded (though not necessarily by their
+     actual length but perhaps by their maximum possible length).  */
+  if (res.range.max < target_int_max ())
+    {
+      res.knownrange = true;
+      /* When the the length of the longest string is known and not
+        excessive use it as the likely length of the string(s).  */
+      res.range.likely = res.range.max;
     }
+  else
+    {
+      /* When the upper bound is unknown (it can be zero or excessive)
+        set the likely length to the greater of 1 and the length of
+        the shortest string and reset the lower bound to zero.  */
+      res.range.likely = res.range.min ? res.range.min : warn_level > 1;
+      res.range.min = 0;
+    }
+
+  res.range.unlikely = unbounded ? HOST_WIDE_INT_MAX : res.range.max;
 
-  return get_string_length (NULL_TREE);
+  return res;
 }
 
 /* Return the minimum and maximum number of characters formatted
@@ -1840,19 +2110,20 @@ get_string_length (tree str)
    vsprinf).  */
 
 static fmtresult
-format_character (const directive &dir, tree arg)
+format_character (const directive &dir, tree arg, vr_values *vr_values)
 {
   fmtresult res;
 
   res.knownrange = true;
 
-  if (dir.modifier == FMT_LEN_l)
+  if (dir.specifier == 'C'
+      || dir.modifier == FMT_LEN_l)
     {
       /* A wide character can result in as few as zero bytes.  */
       res.range.min = 0;
 
       HOST_WIDE_INT min, max;
-      if (get_int_range (arg, integer_type_node, &min, &max, false, 0))
+      if (get_int_range (arg, &min, &max, false, 0, vr_values))
        {
          if (min == 0 && max == 0)
            {
@@ -1861,13 +2132,20 @@ format_character (const directive &dir, tree arg)
              res.range.likely = 0;
              res.range.unlikely = 0;
            }
-         else if (min > 0 && min < 128)
+         else if (min >= 0 && min < 128)
            {
+             /* Be conservative if the target execution character set
+                is not a 1-to-1 mapping to the source character set or
+                if the source set is not ASCII.  */
+             bool one_2_one_ascii
+               = (target_to_host_charmap[0] == 1 && target_to_host ('a') == 97);
+
              /* A wide character in the ASCII range most likely results
                 in a single byte, and only unlikely in up to MB_LEN_MAX.  */
-             res.range.max = 1;
+             res.range.max = one_2_one_ascii ? 1 : target_mb_len_max ();;
              res.range.likely = 1;
              res.range.unlikely = target_mb_len_max ();
+             res.mayfail = !one_2_one_ascii;
            }
          else
            {
@@ -1876,6 +2154,8 @@ format_character (const directive &dir, tree arg)
              res.range.max = target_mb_len_max ();
              res.range.likely = 2;
              res.range.unlikely = res.range.max;
+             /* Converting such a character may fail.  */
+             res.mayfail = true;
            }
        }
       else
@@ -1885,6 +2165,7 @@ format_character (const directive &dir, tree arg)
          res.range.max = target_mb_len_max ();
          res.range.likely = 2;
          res.range.unlikely = res.range.max;
+         res.mayfail = true;
        }
     }
   else
@@ -1905,12 +2186,25 @@ format_character (const directive &dir, tree arg)
    vsprinf).  */
 
 static fmtresult
-format_string (const directive &dir, tree arg)
+format_string (const directive &dir, tree arg, vr_values *)
 {
   fmtresult res;
 
   /* Compute the range the argument's length can be in.  */
-  fmtresult slen = get_string_length (arg);
+  int count_by = 1;
+  if (dir.specifier == 'S' || dir.modifier == FMT_LEN_l)
+    {
+      /* Get a node for a C type that will be the same size
+        as a wchar_t on the target.  */
+      tree node = get_typenode_from_name (MODIFIED_WCHAR_TYPE);
+
+      /* Now that we have a suitable node, get the number of
+        bytes it occupies.  */
+      count_by = int_size_in_bytes (node); 
+      gcc_checking_assert (count_by == 2 || count_by == 4);
+    }
+
+  fmtresult slen = get_string_length (arg, count_by);
   if (slen.range.min == slen.range.max
       && slen.range.min < HOST_WIDE_INT_MAX)
     {
@@ -1920,7 +2214,8 @@ format_string (const directive &dir, tree arg)
       /* A '%s' directive with a string argument with constant length.  */
       res.range = slen.range;
 
-      if (dir.modifier == FMT_LEN_l)
+      if (dir.specifier == 'S'
+         || dir.modifier == FMT_LEN_l)
        {
          /* In the worst case the length of output of a wide string S
             is bounded by MB_LEN_MAX * wcslen (S).  */
@@ -1946,6 +2241,10 @@ format_string (const directive &dir, tree arg)
          /* Even a non-empty wide character string need not convert into
             any bytes.  */
          res.range.min = 0;
+
+         /* A non-empty wide character conversion may fail.  */
+         if (slen.range.max > 0)
+           res.mayfail = true;
        }
       else
        {
@@ -1984,7 +2283,8 @@ format_string (const directive &dir, tree arg)
         at level 2.  This result is adjust upward for width (if it's
         specified).  */
 
-      if (dir.modifier == FMT_LEN_l)
+      if (dir.specifier == 'S'
+         || dir.modifier == FMT_LEN_l)
        {
          /* A wide character converts to as few as zero bytes.  */
          slen.range.min = 0;
@@ -1996,6 +2296,10 @@ format_string (const directive &dir, tree arg)
 
          if (slen.range.likely < target_int_max ())
            slen.range.unlikely *= target_mb_len_max ();
+
+         /* A non-empty wide character conversion may fail.  */
+         if (slen.range.max > 0)
+           res.mayfail = true;
        }
 
       res.range = slen.range;
@@ -2037,6 +2341,8 @@ format_string (const directive &dir, tree arg)
          if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.max)
            res.range.max = dir.prec[1];
          res.range.likely = dir.prec[1] ? warn_level > 1 : 0;
+         if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.unlikely)
+           res.range.unlikely = dir.prec[1];
        }
       else if (slen.range.min >= target_int_max ())
        {
@@ -2046,6 +2352,7 @@ format_string (const directive &dir, tree arg)
             empty, while at level 1 they are assumed to be one byte
             long.  */
          res.range.likely = warn_level > 1;
+         res.range.unlikely = HOST_WIDE_INT_MAX;
        }
       else
        {
@@ -2055,10 +2362,13 @@ format_string (const directive &dir, tree arg)
          if (res.range.likely >= target_int_max ())
            res.range.likely = warn_level > 1;
        }
-
-      res.range.unlikely = res.range.max;
     }
 
+  /* If the argument isn't a nul-terminated string and the number
+     of bytes on output isn't bounded by precision, set NONSTR.  */
+  if (slen.nonstr && slen.range.min < (unsigned HOST_WIDE_INT)dir.prec[0])
+    res.nonstr = slen.nonstr;
+
   /* Bump up the byte counters if WIDTH is greater.  */
   return res.adjust_for_width_or_precision (dir.width);
 }
@@ -2066,7 +2376,7 @@ format_string (const directive &dir, tree arg)
 /* Format plain string (part of the format string itself).  */
 
 static fmtresult
-format_plain (const directive &dir, tree)
+format_plain (const directive &dir, tree, vr_values *)
 {
   fmtresult res (dir.len);
   return res;
@@ -2076,7 +2386,7 @@ format_plain (const directive &dir, tree)
    should be diagnosed given the AVAILable space in the destination.  */
 
 static bool
-should_warn_p (const pass_sprintf_length::call_info &info,
+should_warn_p (const sprintf_dom_walker::call_info &info,
               const result_range &avail, const result_range &result)
 {
   if (result.max <= avail.min)
@@ -2146,8 +2456,8 @@ should_warn_p (const pass_sprintf_length::call_info &info,
    Return true if a warning has been issued.  */
 
 static bool
-maybe_warn (substring_loc &dirloc, source_range *pargrange,
-           const pass_sprintf_length::call_info &info,
+maybe_warn (substring_loc &dirloc, location_t argloc,
+           const sprintf_dom_walker::call_info &info,
            const result_range &avail_range, const result_range &res,
            const directive &dir)
 {
@@ -2170,17 +2480,22 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
                    || (res.max < HOST_WIDE_INT_MAX
                        && avail_range.min < res.max)));
 
+  /* Buffer for the directive in the host character set (used when
+     the source character set is different).  */
+  char hostdir[32];
+
   if (avail_range.min == avail_range.max)
     {
       /* The size of the destination region is exact.  */
       unsigned HOST_WIDE_INT navail = avail_range.max;
 
-      if (*dir.beg != '%')
+      if (target_to_host (*dir.beg) != '%')
        {
          /* For plain character directives (i.e., the format string itself)
             but not others, point the caret at the first character that's
             past the end of the destination.  */
-         dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+         if (navail < dir.len)
+           dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
        }
 
       if (*dir.beg == '\0')
@@ -2188,254 +2503,233 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
          /* This is the terminating nul.  */
          gcc_assert (res.min == 1 && res.min == res.max);
 
-         const char *fmtstr
-           = (info.bounded
-              ? (maybe
-                 ? G_("%qE output may be truncated before the last format "
-                      "character")
-                 : G_("%qE output truncated before the last format character"))
-              : (maybe
-                 ? G_("%qE may write a terminating nul past the end "
-                      "of the destination")
-                 : G_("%qE writing a terminating nul past the end "
-                      "of the destination")));
-
-         return fmtwarn (dirloc, NULL, NULL, info.warnopt (), fmtstr,
+         return fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (),
+                         info.bounded
+                         ? (maybe
+                            ? G_("%qE output may be truncated before the "
+                                 "last format character")
+                            : G_("%qE output truncated before the last "
+                                 "format character"))
+                         : (maybe
+                            ? G_("%qE may write a terminating nul past the "
+                                 "end of the destination")
+                            : G_("%qE writing a terminating nul past the "
+                                 "end of the destination")),
                          info.func);
        }
 
       if (res.min == res.max)
        {
-         const char* fmtstr
-           = (res.min == 1
-              ? (info.bounded
-                 ? (maybe
-                    ? G_("%<%.*s%> directive output may be truncated writing "
-                         "%wu byte into a region of size %wu")
-                    : G_("%<%.*s%> directive output truncated writing "
-                         "%wu byte into a region of size %wu"))
-                 : G_("%<%.*s%> directive writing %wu byte "
-                      "into a region of size %wu"))
-              : (info.bounded
-                 ? (maybe
-                    ? G_("%<%.*s%> directive output may be truncated writing "
-                         "%wu bytes into a region of size %wu")
-                    : 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")));
-         return fmtwarn (dirloc, pargrange, NULL,
-                         info.warnopt (), fmtstr,
-                         dir.len, dir.beg, res.min,
-                         navail);
+         const char *d = target_to_host (hostdir, sizeof hostdir, dir.beg);
+         if (!info.bounded)
+           return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+                             "%<%.*s%> directive writing %wu byte into a "
+                             "region of size %wu",
+                             "%<%.*s%> directive writing %wu bytes into a "
+                             "region of size %wu",
+                             (int) dir.len, d, res.min, navail);
+         else if (maybe)
+           return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+                             "%<%.*s%> directive output may be truncated "
+                             "writing %wu byte into a region of size %wu",
+                             "%<%.*s%> directive output may be truncated "
+                             "writing %wu bytes into a region of size %wu",
+                             (int) dir.len, d, res.min, navail);
+         else
+           return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+                             "%<%.*s%> directive output truncated writing "
+                             "%wu byte into a region of size %wu",
+                             "%<%.*s%> directive output truncated writing "
+                             "%wu bytes into a region of size %wu",
+                             (int) dir.len, d, res.min, navail);
        }
-
       if (res.min == 0 && res.max < maxbytes)
-       {
-         const char* fmtstr
-           = (info.bounded
-              ? (maybe
-                 ? G_("%<%.*s%> directive output may be truncated writing "
-                      "up to %wu bytes into a region of size %wu")
-                 : G_("%<%.*s%> directive output truncated writing "
-                      "up to %wu bytes into a region of size %wu"))
-              : G_("%<%.*s%> directive writing up to %wu bytes "
-                   "into a region of size %wu"));
-         return fmtwarn (dirloc, pargrange, NULL,
-                         info.warnopt (), fmtstr,
-                         dir.len, dir.beg,
-                         res.max, navail);
-       }
+       return fmtwarn (dirloc, argloc, NULL,
+                       info.warnopt (),
+                       info.bounded
+                       ? (maybe
+                          ? G_("%<%.*s%> directive output may be truncated "
+                               "writing up to %wu bytes into a region of "
+                               "size %wu")
+                          : G_("%<%.*s%> directive output truncated writing "
+                               "up to %wu bytes into a region of size %wu"))
+                       : G_("%<%.*s%> directive writing up to %wu bytes "
+                            "into a region of size %wu"), (int) dir.len,
+                       target_to_host (hostdir, sizeof hostdir, dir.beg),
+                       res.max, navail);
 
       if (res.min == 0 && maxbytes <= res.max)
-       {
-         /* This is a special case to avoid issuing the potentially
-            confusing warning:
-              writing 0 or more bytes into a region of size 0.  */
-         const char* fmtstr
-           = (info.bounded
-              ? (maybe
-                 ? G_("%<%.*s%> directive output may be truncated writing "
-                      "likely %wu or more bytes into a region of size %wu")
-                 : G_("%<%.*s%> directive output truncated writing "
-                      "likely %wu or more bytes into a region of size %wu"))
-              : G_("%<%.*s%> directive writing likely %wu or more bytes "
-                   "into a region of size %wu"));
-         return fmtwarn (dirloc, pargrange, NULL,
-                         info.warnopt (), fmtstr,
-                         dir.len, dir.beg,
-                         res.likely, navail);
-       }
+       /* This is a special case to avoid issuing the potentially
+          confusing warning:
+            writing 0 or more bytes into a region of size 0.  */
+       return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                       info.bounded
+                       ? (maybe
+                          ? G_("%<%.*s%> directive output may be truncated "
+                               "writing likely %wu or more bytes into a "
+                               "region of size %wu")
+                          : G_("%<%.*s%> directive output truncated writing "
+                               "likely %wu or more bytes into a region of "
+                               "size %wu"))
+                       : G_("%<%.*s%> directive writing likely %wu or more "
+                            "bytes into a region of size %wu"), (int) dir.len,
+                       target_to_host (hostdir, sizeof hostdir, dir.beg),
+                       res.likely, navail);
 
       if (res.max < maxbytes)
-       {
-         const char* fmtstr
-           = (info.bounded
-              ? (maybe
-                 ? G_("%<%.*s%> directive output may be truncated writing "
-                      "between %wu and %wu bytes into a region of size %wu")
-                 : 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"));
-         return fmtwarn (dirloc, pargrange, NULL,
-                         info.warnopt (), fmtstr,
-                         dir.len, dir.beg,
-                         res.min, res.max,
-                         navail);
-       }
-
-      const char* fmtstr
-       = (info.bounded
-          ? (maybe
-             ? G_("%<%.*s%> directive output may be truncated writing "
-                  "%wu or more bytes into a region of size %wu")
-             : G_("%<%.*s%> directive output truncated writing "
-                  "%wu or more bytes into a region of size %wu"))
-          : G_("%<%.*s%> directive writing %wu or more bytes "
-               "into a region of size %wu"));
-      return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg,
+       return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                       info.bounded
+                       ? (maybe
+                          ? G_("%<%.*s%> directive output may be truncated "
+                               "writing between %wu and %wu bytes into a "
+                               "region of size %wu")
+                          : 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"),
+                       (int) dir.len,
+                       target_to_host (hostdir, sizeof hostdir, dir.beg),
+                       res.min, res.max, navail);
+
+      return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                     info.bounded
+                     ? (maybe
+                        ? G_("%<%.*s%> directive output may be truncated "
+                             "writing %wu or more bytes into a region of "
+                             "size %wu")
+                        : G_("%<%.*s%> directive output truncated writing "
+                             "%wu or more bytes into a region of size %wu"))
+                     : G_("%<%.*s%> directive writing %wu or more bytes "
+                          "into a region of size %wu"), (int) dir.len,
+                     target_to_host (hostdir, sizeof hostdir, dir.beg),
                      res.min, navail);
     }
 
   /* The size of the destination region is a range.  */
 
-  if (*dir.beg != '%')
+  if (target_to_host (*dir.beg) != '%')
     {
       unsigned HOST_WIDE_INT navail = avail_range.max;
 
       /* For plain character directives (i.e., the format string itself)
         but not others, point the caret at the first character that's
         past the end of the destination.  */
-      dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+      if (navail < dir.len)
+       dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
     }
 
   if (*dir.beg == '\0')
     {
       gcc_assert (res.min == 1 && res.min == res.max);
 
-      const char *fmtstr
-       = (info.bounded
-          ? (maybe
-             ? G_("%qE output may be truncated before the last format "
-                  "character")
-             : G_("%qE output truncated before the last format character"))
-          : (maybe
-             ? G_("%qE may write a terminating nul past the end "
-                  "of the destination")
-             : G_("%qE writing a terminating nul past the end "
-                  "of the destination")));
-
-      return fmtwarn (dirloc, NULL, NULL, info.warnopt (), fmtstr,
-                     info.func);
+      return fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (),
+                     info.bounded
+                     ? (maybe
+                        ? G_("%qE output may be truncated before the last "
+                             "format character")
+                        : G_("%qE output truncated before the last format "
+                             "character"))
+                     : (maybe
+                        ? G_("%qE may write a terminating nul past the end "
+                             "of the destination")
+                        : G_("%qE writing a terminating nul past the end "
+                             "of the destination")), info.func);
     }
 
   if (res.min == res.max)
     {
-      const char* fmtstr
-       = (res.min == 1
-          ? (info.bounded
-             ? (maybe
-                ? G_("%<%.*s%> directive output may be truncated writing "
-                     "%wu byte into a region of size between %wu and %wu")
-                : G_("%<%.*s%> directive output truncated writing "
-                     "%wu byte into a region of size between %wu and %wu"))
-             : G_("%<%.*s%> directive writing %wu byte "
-                  "into a region of size between %wu and %wu"))
-          : (info.bounded
-             ? (maybe
-                ? G_("%<%.*s%> directive output may be truncated writing "
-                     "%wu bytes into a region of size between %wu and %wu")
-                : G_("%<%.*s%> directive output truncated writing "
-                     "%wu bytes into a region of size between %wu and %wu"))
-             : G_("%<%.*s%> directive writing %wu bytes "
-                  "into a region of size between %wu and %wu")));
-
-      return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg, res.min,
-                     avail_range.min, avail_range.max);
+      const char *d = target_to_host (hostdir, sizeof hostdir, dir.beg);
+      if (!info.bounded)
+       return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+                         "%<%.*s%> directive writing %wu byte into a region "
+                         "of size between %wu and %wu",
+                         "%<%.*s%> directive writing %wu bytes into a region "
+                         "of size between %wu and %wu", (int) dir.len, d,
+                         res.min, avail_range.min, avail_range.max);
+      else if (maybe)
+       return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+                         "%<%.*s%> directive output may be truncated writing "
+                         "%wu byte into a region of size between %wu and %wu",
+                         "%<%.*s%> directive output may be truncated writing "
+                         "%wu bytes into a region of size between %wu and "
+                         "%wu", (int) dir.len, d, res.min, avail_range.min,
+                         avail_range.max);
+      else
+       return fmtwarn_n (dirloc, argloc, NULL, info.warnopt (), res.min,
+                         "%<%.*s%> directive output truncated writing %wu "
+                         "byte into a region of size between %wu and %wu",
+                         "%<%.*s%> directive output truncated writing %wu "
+                         "bytes into a region of size between %wu and %wu",
+                         (int) dir.len, d, res.min, avail_range.min,
+                         avail_range.max);
     }
 
   if (res.min == 0 && res.max < maxbytes)
-    {
-      const char* fmtstr
-       = (info.bounded
-          ? (maybe
-             ? G_("%<%.*s%> directive output may be truncated writing "
-                  "up to %wu bytes into a region of size between "
-                  "%wu and %wu")
-             : G_("%<%.*s%> directive output truncated writing "
-                  "up to %wu bytes into a region of size between "
-                  "%wu and %wu"))
-          : G_("%<%.*s%> directive writing up to %wu bytes "
-               "into a region of size between %wu and %wu"));
-      return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg, res.max,
-                     avail_range.min, avail_range.max);
-    }
+    return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                   info.bounded
+                   ? (maybe
+                      ? G_("%<%.*s%> directive output may be truncated "
+                           "writing up to %wu bytes into a region of size "
+                           "between %wu and %wu")
+                      : G_("%<%.*s%> directive output truncated writing "
+                           "up to %wu bytes into a region of size between "
+                           "%wu and %wu"))
+                   : G_("%<%.*s%> directive writing up to %wu bytes "
+                        "into a region of size between %wu and %wu"),
+                   (int) dir.len,
+                   target_to_host (hostdir, sizeof hostdir, dir.beg),
+                   res.max, avail_range.min, avail_range.max);
 
   if (res.min == 0 && maxbytes <= res.max)
-    {
-      /* This is a special case to avoid issuing the potentially confusing
-        warning:
-          writing 0 or more bytes into a region of size between 0 and N.  */
-      const char* fmtstr
-       = (info.bounded
-          ? (maybe
-             ? G_("%<%.*s%> directive output may be truncated writing "
-                  "likely %wu or more bytes into a region of size between "
-                  "%wu and %wu")
-             : G_("%<%.*s%> directive output truncated writing likely "
-                  "%wu or more bytes into a region of size between "
-                  "%wu and %wu"))
-          : G_("%<%.*s%> directive writing likely %wu or more bytes "
-               "into a region of size between %wu and %wu"));
-      return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg, res.likely,
-                     avail_range.min, avail_range.max);
-    }
+    /* This is a special case to avoid issuing the potentially confusing
+       warning:
+        writing 0 or more bytes into a region of size between 0 and N.  */
+    return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                   info.bounded
+                   ? (maybe
+                      ? G_("%<%.*s%> directive output may be truncated "
+                           "writing likely %wu or more bytes into a region "
+                           "of size between %wu and %wu")
+                      : G_("%<%.*s%> directive output truncated writing "
+                           "likely %wu or more bytes into a region of size "
+                           "between %wu and %wu"))
+                   : G_("%<%.*s%> directive writing likely %wu or more bytes "
+                        "into a region of size between %wu and %wu"),
+                   (int) dir.len,
+                   target_to_host (hostdir, sizeof hostdir, dir.beg),
+                   res.likely, avail_range.min, avail_range.max);
 
   if (res.max < maxbytes)
-    {
-      const char* fmtstr
-       = (info.bounded
-          ? (maybe
-             ? G_("%<%.*s%> directive output may be truncated writing "
-                  "between %wu and %wu bytes into a region of size "
-                  "between %wu and %wu")
-             : G_("%<%.*s%> directive output truncated writing "
-                  "between %wu and %wu bytes into a region of size "
-                  "between %wu and %wu"))
-          : G_("%<%.*s%> directive writing between %wu and "
-               "%wu bytes into a region of size between %wu and %wu"));
-      return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg,
-                     res.min, res.max,
-                     avail_range.min, avail_range.max);
-    }
-
-  const char* fmtstr
-    = (info.bounded
-       ? (maybe
-         ? G_("%<%.*s%> directive output may be truncated writing "
-              "%wu or more bytes into a region of size between "
-              "%wu and %wu")
-         : G_("%<%.*s%> directive output truncated writing "
-              "%wu or more bytes into a region of size between "
-              "%wu and %wu"))
-       : G_("%<%.*s%> directive writing %wu or more bytes "
-           "into a region of size between %wu and %wu"));
-  return fmtwarn (dirloc, pargrange, NULL,
-                 info.warnopt (), fmtstr,
-                 dir.len, dir.beg,
-                 res.min,
-                 avail_range.min, avail_range.max);
+    return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                   info.bounded
+                   ? (maybe
+                      ? G_("%<%.*s%> directive output may be truncated "
+                           "writing between %wu and %wu bytes into a region "
+                           "of size between %wu and %wu")
+                      : G_("%<%.*s%> directive output truncated writing "
+                           "between %wu and %wu bytes into a region of size "
+                           "between %wu and %wu"))
+                   : G_("%<%.*s%> directive writing between %wu and "
+                        "%wu bytes into a region of size between %wu and "
+                        "%wu"), (int) dir.len,
+                   target_to_host (hostdir, sizeof hostdir, dir.beg),
+                   res.min, res.max, avail_range.min, avail_range.max);
+
+  return fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                 info.bounded
+                 ? (maybe
+                    ? G_("%<%.*s%> directive output may be truncated writing "
+                         "%wu or more bytes into a region of size between "
+                         "%wu and %wu")
+                    : G_("%<%.*s%> directive output truncated writing "
+                         "%wu or more bytes into a region of size between "
+                         "%wu and %wu"))
+                 : G_("%<%.*s%> directive writing %wu or more bytes "
+                      "into a region of size between %wu and %wu"),
+                 (int) dir.len,
+                 target_to_host (hostdir, sizeof hostdir, dir.beg),
+                 res.min, avail_range.min, avail_range.max);
 }
 
 /* Compute the length of the output resulting from the directive DIR
@@ -2443,8 +2737,9 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
    in *RES.  Return true if the directive has been handled.  */
 
 static bool
-format_directive (const pass_sprintf_length::call_info &info,
-                 format_result *res, const directive &dir)
+format_directive (const sprintf_dom_walker::call_info &info,
+                 format_result *res, const directive &dir,
+                 class vr_values *vr_values)
 {
   /* Offset of the beginning of the directive from the beginning
      of the format string.  */
@@ -2457,17 +2752,11 @@ format_directive (const pass_sprintf_length::call_info &info,
   substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
                        offset, start, length);
 
-  /* Also create a location range for the argument if possible.
+  /* Also get the location of the argument if possible.
      This doesn't work for integer literals or function calls.  */
-  source_range argrange;
-  source_range *pargrange;
-  if (dir.arg && CAN_HAVE_LOCATION_P (dir.arg))
-    {
-      argrange = EXPR_LOCATION_RANGE (dir.arg);
-      pargrange = &argrange;
-    }
-  else
-    pargrange = NULL;
+  location_t argloc = UNKNOWN_LOCATION;
+  if (dir.arg)
+    argloc = EXPR_LOCATION (dir.arg);
 
   /* Bail when there is no function to compute the output length,
      or when minimum length checking has been disabled.   */
@@ -2475,7 +2764,7 @@ format_directive (const pass_sprintf_length::call_info &info,
     return false;
 
   /* Compute the range of lengths of the formatted output.  */
-  fmtresult fmtres = dir.fmtfunc (dir, dir.arg);
+  fmtresult fmtres = dir.fmtfunc (dir, dir.arg, vr_values);
 
   /* Record whether the output of all directives is known to be
      bounded by some maximum, implying that their arguments are
@@ -2516,13 +2805,18 @@ format_directive (const pass_sprintf_length::call_info &info,
        }
     }
 
+  /* Buffer for the directive in the host character set (used when
+     the source character set is different).  */
+  char hostdir[32];
+
   int dirlen = dir.len;
 
   if (fmtres.nullp)
     {
-      fmtwarn (dirloc, pargrange, NULL, info.warnopt (),
-              "%<%.*s%> directive argument is null",
-              dirlen, dir.beg);
+      fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+              "%G%<%.*s%> directive argument is null",
+              info.callstmt, dirlen,
+              target_to_host (hostdir, sizeof hostdir, dir.beg));
 
       /* Don't bother processing the rest of the format string.  */
       res->warned = true;
@@ -2539,7 +2833,7 @@ format_directive (const pass_sprintf_length::call_info &info,
   bool warned = res->warned;
 
   if (!warned)
-    warned = maybe_warn (dirloc, pargrange, info, avail_range,
+    warned = maybe_warn (dirloc, argloc, info, avail_range,
                         fmtres.range, dir);
 
   /* Bump up the total maximum if it isn't too big.  */
@@ -2565,15 +2859,20 @@ format_directive (const pass_sprintf_length::call_info &info,
      of 4095 bytes required to be supported?  */
   bool minunder4k = fmtres.range.min < 4096;
   bool maxunder4k = fmtres.range.max < 4096;
-  /* Clear UNDER4K in the overall result if the maximum has exceeded
-     the 4k (this is necessary to avoid the return valuye optimization
+  /* Clear POSUNDER4K in the overall result if the maximum has exceeded
+     the 4k (this is necessary to avoid the return value optimization
      that may not be safe in the maximum case).  */
   if (!maxunder4k)
-    res->under4k = false;
+    res->posunder4k = false;
+  /* Also clear POSUNDER4K if the directive may fail.  */
+  if (fmtres.mayfail)
+    res->posunder4k = false;
 
   if (!warned
       /* Only warn at level 2.  */
-      && 1 < warn_level
+      && warn_level > 1
+      /* Only warn for string functions.  */
+      && info.is_string_func ()
       && (!minunder4k
          || (!maxunder4k && fmtres.range.max < HOST_WIDE_INT_MAX)))
     {
@@ -2582,28 +2881,31 @@ format_directive (const pass_sprintf_length::call_info &info,
         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).  */
+        (like Glibc does with very large precision or width).
+        Issue the "may exceed" warning only for string functions and
+        not for fprintf or printf.  */
 
       if (fmtres.range.min == fmtres.range.max)
-       warned = fmtwarn (dirloc, pargrange, NULL,
-                         info.warnopt (),
+       warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
                          "%<%.*s%> directive output of %wu bytes exceeds "
-                         "minimum required size of 4095",
-                         dirlen, dir.beg, fmtres.range.min);
-      else
-       {
-         const char *fmtstr
-           = (minunder4k
-              ? G_("%<%.*s%> directive output between %wu and %wu "
-                   "bytes may exceed minimum required size of 4095")
-              : G_("%<%.*s%> directive output between %wu and %wu "
-                   "bytes exceeds minimum required size of 4095"));
-
-         warned = fmtwarn (dirloc, pargrange, NULL,
-                           info.warnopt (), fmtstr,
-                           dirlen, dir.beg,
-                           fmtres.range.min, fmtres.range.max);
-       }
+                         "minimum required size of 4095", dirlen,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
+                         fmtres.range.min);
+      else if (!minunder4k)
+       warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                         "%<%.*s%> directive output between %wu and %wu "
+                         "bytes exceeds minimum required size of 4095",
+                         dirlen,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
+                         fmtres.range.min, fmtres.range.max);
+      else if (!info.retval_used () && info.is_string_func ())
+       warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                         "%<%.*s%> directive output between %wu and %wu "
+                         "bytes may exceed minimum required size of "
+                         "4095",
+                         dirlen,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
+                         fmtres.range.min, fmtres.range.max);
     }
 
   /* Has the likely and maximum directive output exceeded INT_MAX?  */
@@ -2619,42 +2921,75 @@ format_directive (const pass_sprintf_length::call_info &info,
       /* Warn for the likely output size at level 1.  */
       && (likelyximax
          /* But only warn for the maximum at level 2.  */
-         || (1 < warn_level
+         || (warn_level > 1
              && maxximax
              && fmtres.range.max < HOST_WIDE_INT_MAX)))
     {
-      /* The directive output causes the total length of output
-        to exceed INT_MAX bytes.  */
-
-      if (fmtres.range.min == fmtres.range.max)
-       warned = fmtwarn (dirloc, pargrange, NULL, info.warnopt (),
-                         "%<%.*s%> directive output of %wu bytes causes "
-                         "result to exceed %<INT_MAX%>",
-                         dirlen, dir.beg, fmtres.range.min);
-      else
+      if (fmtres.range.min > target_int_max ())
+       {
+         /* The directive output exceeds INT_MAX bytes.  */
+         if (fmtres.range.min == fmtres.range.max)
+           warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                             "%<%.*s%> directive output of %wu bytes exceeds "
+                             "%<INT_MAX%>", dirlen,
+                             target_to_host (hostdir, sizeof hostdir, dir.beg),
+                             fmtres.range.min);
+         else
+           warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                             "%<%.*s%> directive output between %wu and "
+                             "%wu bytes exceeds %<INT_MAX%>", dirlen,
+                             target_to_host (hostdir, sizeof hostdir, dir.beg),
+                             fmtres.range.min, fmtres.range.max);
+       }
+      else if (res->range.min > target_int_max ())
        {
-         const char *fmtstr
-           = (fmtres.range.min > target_int_max ()
-              ? 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%>"));
-         warned = fmtwarn (dirloc, pargrange, NULL,
-                           info.warnopt (), fmtstr,
-                           dirlen, dir.beg,
-                           fmtres.range.min, fmtres.range.max);
+         /* The directive output is under INT_MAX but causes the result
+            to exceed INT_MAX bytes.  */
+         if (fmtres.range.min == fmtres.range.max)
+           warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                             "%<%.*s%> directive output of %wu bytes causes "
+                             "result to exceed %<INT_MAX%>", dirlen,
+                             target_to_host (hostdir, sizeof hostdir, dir.beg),
+                             fmtres.range.min);
+         else
+           warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                             "%<%.*s%> directive output between %wu and "
+                             "%wu bytes causes result to exceed %<INT_MAX%>",
+                             dirlen,
+                             target_to_host (hostdir, sizeof hostdir, dir.beg),
+                             fmtres.range.min, fmtres.range.max);
        }
+      else if ((!info.retval_used () || !info.bounded)
+              && (info.is_string_func ()))
+       /* Warn for calls to string functions that either aren't bounded
+          (sprintf) or whose return value isn't used.  */
+       warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                         "%<%.*s%> directive output between %wu and "
+                         "%wu bytes may cause result to exceed "
+                         "%<INT_MAX%>", dirlen,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
+                         fmtres.range.min, fmtres.range.max);
+    }
+
+  if (!warned && fmtres.nonstr)
+    {
+      warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
+                       "%<%.*s%> directive argument is not a nul-terminated "
+                       "string",
+                       dirlen,
+                       target_to_host (hostdir, sizeof hostdir, dir.beg));
+      if (warned && DECL_P (fmtres.nonstr))
+       inform (DECL_SOURCE_LOCATION (fmtres.nonstr),
+               "referenced argument declared here");
+      return false;
     }
 
   if (warned && fmtres.range.min < fmtres.range.likely
       && fmtres.range.likely < fmtres.range.max)
-    {
-      inform (info.fmtloc,
-             (1 == fmtres.range.likely
-              ? G_("assuming directive output of %wu byte")
-              : G_("assuming directive output of %wu bytes")),
+    inform_n (info.fmtloc, fmtres.range.likely,
+             "assuming directive output of %wu byte",
+             "assuming directive output of %wu bytes",
              fmtres.range.likely);
-    }
 
   if (warned && fmtres.argmin)
     {
@@ -2671,69 +3006,92 @@ format_directive (const pass_sprintf_length::call_info &info,
 
   res->warned |= warned;
 
-  if (!dir.beg[0] && res->warned && info.objsize < HOST_WIDE_INT_MAX)
+  if (!dir.beg[0] && res->warned)
     {
-      /* 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->range.min;
       unsigned HOST_WIDE_INT max = res->range.max;
 
-      if (min == max)
-       inform (callloc,
-               (min == 1
-                ? G_("%qE output %wu byte into a destination of size %wu")
-                : G_("%qE output %wu bytes into a destination of size %wu")),
-               info.func, min, info.objsize);
-      else if (max < HOST_WIDE_INT_MAX)
-       inform (callloc,
-               "%qE output between %wu and %wu bytes into "
-               "a destination of size %wu",
-               info.func, min, max, info.objsize);
-      else if (min < res->range.likely && res->range.likely < max)
-       inform (callloc,
-               "%qE output %wu or more bytes (assuming %wu) into "
-               "a destination of size %wu",
-               info.func, min, res->range.likely, info.objsize);
-      else
-       inform (callloc,
-               "%qE output %wu or more bytes into a destination of size %wu",
-               info.func, min, info.objsize);
+      if (info.objsize < HOST_WIDE_INT_MAX)
+       {
+         /* If a warning has been issued for buffer overflow or truncation
+            help the user figure out how big a buffer they need.  */
+
+         if (min == max)
+           inform_n (callloc, min,
+                     "%qE output %wu byte into a destination of size %wu",
+                     "%qE output %wu bytes into a destination of size %wu",
+                     info.func, min, info.objsize);
+         else if (max < HOST_WIDE_INT_MAX)
+           inform (callloc,
+                   "%qE output between %wu and %wu bytes into "
+                   "a destination of size %wu",
+                   info.func, min, max, info.objsize);
+         else if (min < res->range.likely && res->range.likely < max)
+           inform (callloc,
+                   "%qE output %wu or more bytes (assuming %wu) into "
+                   "a destination of size %wu",
+                   info.func, min, res->range.likely, info.objsize);
+         else
+           inform (callloc,
+                   "%qE output %wu or more bytes into a destination of size "
+                   "%wu",
+                   info.func, min, info.objsize);
+       }
+      else if (!info.is_string_func ())
+       {
+         /* If the warning is for a file function function like fprintf
+            of printf with no destination size just print the computed
+            result.  */
+         if (min == max)
+           inform_n (callloc, min,
+                     "%qE output %wu byte", "%qE output %wu bytes",
+                     info.func, min);
+         else if (max < HOST_WIDE_INT_MAX)
+           inform (callloc,
+                   "%qE output between %wu and %wu bytes",
+                   info.func, min, max);
+         else if (min < res->range.likely && res->range.likely < max)
+           inform (callloc,
+                   "%qE output %wu or more bytes (assuming %wu)",
+                   info.func, min, res->range.likely);
+         else
+           inform (callloc,
+                   "%qE output %wu or more bytes",
+                   info.func, min);
+       }
     }
 
   if (dump_file && *dir.beg)
     {
-      fprintf (dump_file, "    Result: %lli, %lli, %lli, %lli "
-              "(%lli, %lli, %lli, %lli)\n",
-              (long long)fmtres.range.min,
-              (long long)fmtres.range.likely,
-              (long long)fmtres.range.max,
-              (long long)fmtres.range.unlikely,
-              (long long)res->range.min,
-              (long long)res->range.likely,
-              (long long)res->range.max,
-              (long long)res->range.unlikely);
+      fprintf (dump_file,
+              "    Result: "
+              HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC ", "
+              HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC " ("
+              HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC ", "
+              HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC ")\n",
+              fmtres.range.min, fmtres.range.likely,
+              fmtres.range.max, fmtres.range.unlikely,
+              res->range.min, res->range.likely,
+              res->range.max, res->range.unlikely);
     }
 
   return true;
 }
 
-#pragma GCC diagnostic pop
-
 /* Parse a format directive in function call described by INFO starting
    at STR and populate DIR structure.  Bump up *ARGNO by the number of
    arguments extracted for the directive.  Return the length of
    the directive.  */
 
 static size_t
-parse_directive (pass_sprintf_length::call_info &info,
+parse_directive (sprintf_dom_walker::call_info &info,
                 directive &dir, format_result *res,
-                const char *str, unsigned *argno)
+                const char *str, unsigned *argno,
+                vr_values *vr_values)
 {
-  const char *pcnt = strchr (str, '%');
+  const char *pcnt = strchr (str, target_percent);
   dir.beg = str;
 
   if (size_t len = pcnt ? pcnt - str : *str ? strlen (str) : 1)
@@ -2746,11 +3104,12 @@ parse_directive (pass_sprintf_length::call_info &info,
 
       if (dump_file)
        {
-         fprintf (dump_file, "  Directive %u at offset %llu: \"%.*s\", "
-                  "length = %llu\n",
+         fprintf (dump_file, "  Directive %u at offset "
+                  HOST_WIDE_INT_PRINT_UNSIGNED ": \"%.*s\", "
+                  "length = " HOST_WIDE_INT_PRINT_UNSIGNED "\n",
                   dir.dirno,
-                  (unsigned long long)(size_t)(dir.beg - info.fmtstr),
-                  (int)dir.len, dir.beg, (unsigned long long)dir.len);
+                  (unsigned HOST_WIDE_INT)(size_t)(dir.beg - info.fmtstr),
+                  (int)dir.len, dir.beg, (unsigned HOST_WIDE_INT) dir.len);
        }
 
       return len - !*str;
@@ -2759,7 +3118,7 @@ parse_directive (pass_sprintf_length::call_info &info,
   const char *pf = pcnt + 1;
 
     /* POSIX numbered argument index or zero when none.  */
-  unsigned dollar = 0;
+  HOST_WIDE_INT dollar = 0;
 
   /* With and precision.  -1 when not specified, HOST_WIDE_INT_MIN
      when given by a va_list argument, and a non-negative value
@@ -2767,6 +3126,17 @@ parse_directive (pass_sprintf_length::call_info &info,
   HOST_WIDE_INT width = -1;
   HOST_WIDE_INT precision = -1;
 
+  /* Pointers to the beginning of the width and precision decimal
+     string (if any) within the directive.  */
+  const char *pwidth = 0;
+  const char *pprec = 0;
+
+  /* When the value of the decimal string that specifies width or
+     precision is out of range, points to the digit that causes
+     the value to exceed the limit.  */
+  const char *werange = NULL;
+  const char *perange = NULL;
+
   /* Width specified via the asterisk.  Need not be INTEGER_CST.
      For vararg functions set to void_node.  */
   tree star_width = NULL_TREE;
@@ -2775,17 +3145,16 @@ parse_directive (pass_sprintf_length::call_info &info,
      For vararg functions set to void_node.  */
   tree star_precision = NULL_TREE;
 
-  if (ISDIGIT (*pf))
+  if (ISDIGIT (target_to_host (*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;
-      width = strtol (pf, &end, 10);
-      pf = end;
+      pwidth = pf;
+      width = target_strtowi (&pf, &werange);
     }
-  else if ('*' == *pf)
+  else if (target_to_host (*pf) == '*')
     {
       /* Similarly to the block above, this could be either a POSIX
         positional argument or a width, depending on what follows.  */
@@ -2796,20 +3165,22 @@ parse_directive (pass_sprintf_length::call_info &info,
       ++pf;
     }
 
-  if (*pf == '$')
+  if (target_to_host (*pf) == '$')
     {
       /* Handle the POSIX dollar sign which references the 1-based
         positional argument number.  */
       if (width != -1)
        dollar = width + info.argidx;
       else if (star_width
-              && TREE_CODE (star_width) == INTEGER_CST)
+              && TREE_CODE (star_width) == INTEGER_CST
+              && (TYPE_PRECISION (TREE_TYPE (star_width))
+                  <= TYPE_PRECISION (integer_type_node)))
        dollar = width + tree_to_shwi (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 == (int)info.argidx
          || dollar > gimple_call_num_args (info.callstmt))
        return false;
 
@@ -2843,14 +3214,14 @@ parse_directive (pass_sprintf_length::call_info &info,
         the next field is the optional flags followed by an optional
         width.  */
       for ( ; ; ) {
-       switch (*pf)
+       switch (target_to_host (*pf))
          {
          case ' ':
          case '0':
          case '+':
          case '-':
          case '#':
-           dir.set_flag (*pf++);
+           dir.set_flag (target_to_host (*pf++));
            break;
 
          default:
@@ -2859,13 +3230,13 @@ parse_directive (pass_sprintf_length::call_info &info,
       }
 
     start_width:
-      if (ISDIGIT (*pf))
+      if (ISDIGIT (target_to_host (*pf)))
        {
-         char *end;
-         width = strtol (pf, &end, 10);
-         pf = end;
+         werange = 0;
+         pwidth = pf;
+         width = target_strtowi (&pf, &werange);
        }
-      else if ('*' == *pf)
+      else if (target_to_host (*pf) == '*')
        {
          if (*argno < gimple_call_num_args (info.callstmt))
            star_width = gimple_call_arg (info.callstmt, (*argno)++);
@@ -2877,7 +3248,7 @@ parse_directive (pass_sprintf_length::call_info &info,
            }
          ++pf;
        }
-      else if ('\'' == *pf)
+      else if (target_to_host (*pf) == '\'')
        {
          /* The POSIX apostrophe indicating a numeric grouping
             in the current locale.  Even though it's possible to
@@ -2889,17 +3260,16 @@ parse_directive (pass_sprintf_length::call_info &info,
     }
 
  start_precision:
-  if ('.' == *pf)
+  if (target_to_host (*pf) == '.')
     {
       ++pf;
 
-      if (ISDIGIT (*pf))
+      if (ISDIGIT (target_to_host (*pf)))
        {
-         char *end;
-         precision = strtol (pf, &end, 10);
-         pf = end;
+         pprec = pf;
+         precision = target_strtowi (&pf, &perange);
        }
-      else if ('*' == *pf)
+      else if (target_to_host (*pf) == '*')
        {
          if (*argno < gimple_call_num_args (info.callstmt))
            star_precision = gimple_call_arg (info.callstmt, (*argno)++);
@@ -2919,10 +3289,10 @@ parse_directive (pass_sprintf_length::call_info &info,
        }
     }
 
-  switch (*pf)
+  switch (target_to_host (*pf))
     {
     case 'h':
-      if (pf[1] == 'h')
+      if (target_to_host (pf[1]) == 'h')
        {
          ++pf;
          dir.modifier = FMT_LEN_hh;
@@ -2943,7 +3313,7 @@ parse_directive (pass_sprintf_length::call_info &info,
       break;
 
     case 'l':
-      if (pf[1] == 'l')
+      if (target_to_host (pf[1]) == 'l')
        {
          ++pf;
          dir.modifier = FMT_LEN_ll;
@@ -2964,7 +3334,7 @@ parse_directive (pass_sprintf_length::call_info &info,
       break;
     }
 
-  switch (*pf)
+  switch (target_to_host (*pf))
     {
       /* Handle a sole '%' character the same as "%%" but since it's
         undefined prevent the result from being folded.  */
@@ -3011,12 +3381,15 @@ parse_directive (pass_sprintf_length::call_info &info,
       dir.fmtfunc = format_none;
       break;
 
+    case 'C':
     case 'c':
+      /* POSIX wide character and C/POSIX narrow character.  */
       dir.fmtfunc = format_character;
       break;
 
     case 'S':
     case 's':
+      /* POSIX wide string and C/POSIX narrow character string.  */
       dir.fmtfunc = format_string;
       break;
 
@@ -3025,12 +3398,19 @@ parse_directive (pass_sprintf_length::call_info &info,
       return 0;
     }
 
-  dir.specifier = *pf++;
+  dir.specifier = target_to_host (*pf++);
+
+  /* Store the length of the format directive.  */
+  dir.len = pf - pcnt;
+
+  /* Buffer for the directive in the host character set (used when
+     the source character set is different).  */
+  char hostdir[32];
 
   if (star_width)
     {
-      if (TREE_CODE (TREE_TYPE (star_width)) == INTEGER_TYPE)
-       dir.set_width (star_width);
+      if (INTEGRAL_TYPE_P (TREE_TYPE (star_width)))
+       dir.set_width (star_width, vr_values);
       else
        {
          /* Width specified by a va_list takes on the range [0, -INT_MIN]
@@ -3040,12 +3420,30 @@ parse_directive (pass_sprintf_length::call_info &info,
        }
     }
   else
-    dir.set_width (width);
+    {
+      if (width == HOST_WIDE_INT_MAX && werange)
+       {
+         size_t begin = dir.beg - info.fmtstr + (pwidth - pcnt);
+         size_t caret = begin + (werange - pcnt);
+         size_t end = pf - info.fmtstr - 1;
+
+         /* Create a location for the width part of the directive,
+            pointing the caret at the first out-of-range digit.  */
+         substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
+                               caret, begin, end);
+
+         fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (),
+                  "%<%.*s%> directive width out of range", (int) dir.len,
+                  target_to_host (hostdir, sizeof hostdir, dir.beg));
+       }
+
+      dir.set_width (width);
+    }
 
   if (star_precision)
     {
-      if (TREE_CODE (TREE_TYPE (star_precision)) == INTEGER_TYPE)
-       dir.set_precision (star_precision);
+      if (INTEGRAL_TYPE_P (TREE_TYPE (star_precision)))
+       dir.set_precision (star_precision, vr_values);
       else
        {
          /* Precision specified by a va_list takes on the range [-1, INT_MAX]
@@ -3055,7 +3453,26 @@ parse_directive (pass_sprintf_length::call_info &info,
        }
     }
   else
-    dir.set_precision (precision);
+    {
+      if (precision == HOST_WIDE_INT_MAX && perange)
+       {
+         size_t begin = dir.beg - info.fmtstr + (pprec - pcnt) - 1;
+         size_t caret = dir.beg - info.fmtstr + (perange - pcnt) - 1;
+         size_t end = pf - info.fmtstr - 2;
+
+         /* Create a location for the precision part of the directive,
+            including the leading period, pointing the caret at the first
+            out-of-range digit .  */
+         substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
+                               caret, begin, end);
+
+         fmtwarn (dirloc, UNKNOWN_LOCATION, NULL, info.warnopt (),
+                  "%<%.*s%> directive precision out of range", (int) dir.len,
+                  target_to_host (hostdir, sizeof hostdir, dir.beg));
+       }
+
+      dir.set_precision (precision);
+    }
 
   /* Extract the argument if the directive takes one and if it's
      available (e.g., the function doesn't take a va_list).  Treat
@@ -3065,30 +3482,36 @@ parse_directive (pass_sprintf_length::call_info &info,
       && *argno < gimple_call_num_args (info.callstmt))
     dir.arg = gimple_call_arg (info.callstmt, dollar ? dollar : (*argno)++);
 
-  /* Return the length of the format directive.  */
-  dir.len = pf - pcnt;
-
   if (dump_file)
     {
-      fprintf (dump_file, "  Directive %u at offset %llu: \"%.*s\"",
-              dir.dirno, (unsigned long long)(size_t)(dir.beg - info.fmtstr),
+      fprintf (dump_file,
+              "  Directive %u at offset " HOST_WIDE_INT_PRINT_UNSIGNED
+              ": \"%.*s\"",
+              dir.dirno,
+              (unsigned HOST_WIDE_INT)(size_t)(dir.beg - info.fmtstr),
               (int)dir.len, dir.beg);
       if (star_width)
        {
          if (dir.width[0] == dir.width[1])
-           fprintf (dump_file, ", width = %lli", (long long)dir.width[0]);
+           fprintf (dump_file, ", width = " HOST_WIDE_INT_PRINT_DEC,
+                    dir.width[0]);
          else
-           fprintf (dump_file, ", width in range [%lli, %lli]",
-                    (long long)dir.width[0], (long long)dir.width[1]);
+           fprintf (dump_file,
+                    ", width in range [" HOST_WIDE_INT_PRINT_DEC
+                    ", " HOST_WIDE_INT_PRINT_DEC "]",
+                    dir.width[0], dir.width[1]);
        }
 
       if (star_precision)
        {
          if (dir.prec[0] == dir.prec[1])
-           fprintf (dump_file, ", precision = %lli", (long long)dir.prec[0]);
+           fprintf (dump_file, ", precision = " HOST_WIDE_INT_PRINT_DEC,
+                    dir.prec[0]);
          else
-           fprintf (dump_file, ", precision in range [%lli, %lli]",
-                    (long long)dir.prec[0], (long long)dir.prec[1]);
+           fprintf (dump_file,
+                    ", precision in range [" HOST_WIDE_INT_PRINT_DEC
+                    HOST_WIDE_INT_PRINT_DEC "]",
+                    dir.prec[0], dir.prec[1]);
        }
       fputc ('\n', dump_file);
     }
@@ -3104,8 +3527,8 @@ parse_directive (pass_sprintf_length::call_info &info,
    that caused the processing to be terminated early).  */
 
 bool
-pass_sprintf_length::compute_format_length (call_info &info,
-                                           format_result *res)
+sprintf_dom_walker::compute_format_length (call_info &info,
+                                          format_result *res)
 {
   if (dump_file)
     {
@@ -3114,18 +3537,19 @@ pass_sprintf_length::compute_format_length (call_info &info,
               LOCATION_FILE (callloc), LOCATION_LINE (callloc));
       print_generic_expr (dump_file, info.func, dump_flags);
 
-      fprintf (dump_file, ": objsize = %llu, fmtstr = \"%s\"\n",
-              (unsigned long long)info.objsize, info.fmtstr);
+      fprintf (dump_file,
+              ": objsize = " HOST_WIDE_INT_PRINT_UNSIGNED
+              ", fmtstr = \"%s\"\n",
+              info.objsize, info.fmtstr);
     }
 
   /* Reset the minimum and maximum byte counters.  */
   res->range.min = res->range.max = 0;
 
   /* No directive has been seen yet so the length of output is bounded
-     by the known range [0, 0] (with no conversion producing more than
-     4K bytes) until determined otherwise.  */
+     by the known range [0, 0] (with no conversion resulting in a failure
+     or producing more than 4K bytes) until determined otherwise.  */
   res->knownrange = true;
-  res->under4k = true;
   res->floating = false;
   res->warned = false;
 
@@ -3140,10 +3564,12 @@ pass_sprintf_length::compute_format_length (call_info &info,
       directive dir = directive ();
       dir.dirno = dirno;
 
-      size_t n = parse_directive (info, dir, res, pf, &argno);
+      size_t n = parse_directive (info, dir, res, pf, &argno,
+                                 evrp_range_analyzer.get_vr_values ());
 
       /* Return failure if the format function fails.  */
-      if (!format_directive (info, res, dir))
+      if (!format_directive (info, res, dir,
+                            evrp_range_analyzer.get_vr_values ()))
        return false;
 
       /* Return success the directive is zero bytes long and it's
@@ -3161,11 +3587,15 @@ pass_sprintf_length::compute_format_length (call_info &info,
 }
 
 /* Return the size of the object referenced by the expression DEST if
-   available, or -1 otherwise.  */
+   available, or the maximum possible size otherwise.  */
 
 static unsigned HOST_WIDE_INT
 get_destination_size (tree dest)
 {
+  /* When there is no destination return the maximum.  */
+  if (!dest)
+    return HOST_WIDE_INT_MAX;
+
   /* Initialize object size info before trying to compute it.  */
   init_object_sizes ();
 
@@ -3179,34 +3609,28 @@ get_destination_size (tree dest)
   if (compute_builtin_object_size (dest, ost, &size))
     return size;
 
-  return HOST_WIDE_INT_M1U;
+  return HOST_WIDE_INT_MAX;
 }
 
-/* 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.
-   Return true if the call is removed and gsi_next should not be performed
-   in the caller.  */
+/* Return true if the call described by INFO with result RES safe to
+   optimize (i.e., no undefined behavior), and set RETVAL to the range
+   of its return values.  */
 
 static bool
-try_substitute_return_value (gimple_stmt_iterator *gsi,
-                            const pass_sprintf_length::call_info &info,
-                            const format_result &res)
+is_call_safe (const sprintf_dom_walker::call_info &info,
+             const format_result &res, bool under4k,
+             unsigned HOST_WIDE_INT retval[2])
 {
-  tree lhs = gimple_get_lhs (info.callstmt);
-
-  /* Set to true when the entire call has been removed.  */
-  bool removed = false;
+  if (under4k && !res.posunder4k)
+    return false;
 
   /* The minimum return value.  */
-  unsigned HOST_WIDE_INT minretval = res.range.min;
+  retval[0] = res.range.min;
 
   /* The maximum return value is in most cases bounded by RES.RANGE.MAX
      but in cases involving multibyte characters could be as large as
      RES.RANGE.UNLIKELY.  */
-  unsigned HOST_WIDE_INT maxretval
+  retval[1]
     = res.range.unlikely < res.range.max ? res.range.max : res.range.unlikely;
 
   /* Adjust the number of bytes which includes the terminating nul
@@ -3215,10 +3639,10 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
      a valid range before the adjustment below is [0, INT_MAX + 1]
      (the functions only return negative values on error or undefined
      behavior).  */
-  if (minretval <= target_int_max () + 1)
-    --minretval;
-  if (maxretval <= target_int_max () + 1)
-    --maxretval;
+  if (retval[0] <= target_int_max () + 1)
+    --retval[0];
+  if (retval[1] <= target_int_max () + 1)
+    --retval[1];
 
   /* Avoid the return value optimization when the behavior of the call
      is undefined either because any directive may have produced 4K or
@@ -3226,19 +3650,55 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
      the output overflows the destination object (but leave it enabled
      when the function is bounded because then the behavior is well-
      defined).  */
-  if (res.under4k
-      && minretval == maxretval
-      && (info.bounded || minretval < info.objsize)
-      && minretval <= target_int_max ()
+  if (retval[0] == retval[1]
+      && (info.bounded || retval[0] < info.objsize)
+      && retval[0] <= target_int_max ())
+    return true;
+
+  if ((info.bounded || retval[1] < info.objsize)
+      && (retval[0] < target_int_max ()
+         && retval[1] < target_int_max ()))
+    return true;
+
+  if (!under4k && (info.bounded || retval[0] < info.objsize))
+    return true;
+
+  return false;
+}
+
+/* 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.
+   Return true if the call is removed and gsi_next should not be performed
+   in the caller.  */
+
+static bool
+try_substitute_return_value (gimple_stmt_iterator *gsi,
+                            const sprintf_dom_walker::call_info &info,
+                            const format_result &res)
+{
+  tree lhs = gimple_get_lhs (info.callstmt);
+
+  /* Set to true when the entire call has been removed.  */
+  bool removed = false;
+
+  /* The minimum and maximum return value.  */
+  unsigned HOST_WIDE_INT retval[2];
+  bool safe = is_call_safe (info, res, true, retval);
+
+  if (safe
+      && retval[0] == retval[1]
       /* Not prepared to handle possibly throwing calls here; they shouldn't
         appear in non-artificial testcases, except when the __*_chk routines
         are badly declared.  */
       && !stmt_ends_bb_p (info.callstmt))
     {
-      tree cst = build_int_cst (integer_type_node, minretval);
+      tree cst = build_int_cst (lhs ? TREE_TYPE (lhs) : integer_type_node,
+                               retval[0]);
 
-      if (lhs == NULL_TREE
-         && info.nowrite)
+      if (lhs == NULL_TREE && info.nowrite)
        {
          /* Remove the call to the bounded function with a zero size
             (e.g., snprintf(0, 0, "%i", 123)) if there is no lhs.  */
@@ -3279,22 +3739,22 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
            }
        }
     }
-  else if (lhs)
+  else if (lhs && types_compatible_p (TREE_TYPE (lhs), integer_type_node))
     {
       bool setrange = false;
 
-      if ((info.bounded || maxretval < info.objsize)
-         && res.under4k
-         && (minretval < target_int_max ()
-             && maxretval < target_int_max ()))
+      if (safe
+         && (info.bounded || retval[1] < info.objsize)
+         && (retval[0] < target_int_max ()
+             && retval[1] < target_int_max ()))
        {
          /* 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);
 
-         wide_int min = wi::shwi (minretval, prec);
-         wide_int max = wi::shwi (maxretval, prec);
+         wide_int min = wi::shwi (retval[0], prec);
+         wide_int max = wi::shwi (retval[1], prec);
          set_range_info (lhs, VR_RANGE, min, max);
 
          setrange = true;
@@ -3303,21 +3763,22 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
       if (dump_file)
        {
          const char *inbounds
-           = (minretval < info.objsize
-              ? (maxretval < info.objsize
+           = (retval[0] < info.objsize
+              ? (retval[1] < info.objsize
                  ? "in" : "potentially out-of")
               : "out-of");
 
          const char *what = setrange ? "Setting" : "Discarding";
-         if (minretval != maxretval)
+         if (retval[0] != retval[1])
            fprintf (dump_file,
-                    "  %s %s-bounds return value range [%llu, %llu].\n",
-                    what, inbounds,
-                    (unsigned long long)minretval,
-                    (unsigned long long)maxretval);
+                    "  %s %s-bounds return value range ["
+                    HOST_WIDE_INT_PRINT_UNSIGNED ", "
+                    HOST_WIDE_INT_PRINT_UNSIGNED "].\n",
+                    what, inbounds, retval[0], retval[1]);
          else
-           fprintf (dump_file, "  %s %s-bounds return value %llu.\n",
-                    what, inbounds, (unsigned long long)minretval);
+           fprintf (dump_file, "  %s %s-bounds return value "
+                    HOST_WIDE_INT_PRINT_UNSIGNED ".\n",
+                    what, inbounds, retval[0]);
        }
     }
 
@@ -3327,21 +3788,96 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
   return removed;
 }
 
+/* Try to simplify a s{,n}printf call described by INFO with result
+   RES by replacing it with a simpler and presumably more efficient
+   call (such as strcpy).  */
+
+static bool
+try_simplify_call (gimple_stmt_iterator *gsi,
+                  const sprintf_dom_walker::call_info &info,
+                  const format_result &res)
+{
+  unsigned HOST_WIDE_INT dummy[2];
+  if (!is_call_safe (info, res, info.retval_used (), dummy))
+    return false;
+
+  switch (info.fncode)
+    {
+    case BUILT_IN_SNPRINTF:
+      return gimple_fold_builtin_snprintf (gsi);
+
+    case BUILT_IN_SPRINTF:
+      return gimple_fold_builtin_sprintf (gsi);
+
+    default:
+      ;
+    }
+
+  return false;
+}
+
+/* Return the zero-based index of the format string argument of a printf
+   like function and set *IDX_ARGS to the first format argument.  When
+   no such index exists return UINT_MAX.  */
+
+static unsigned
+get_user_idx_format (tree fndecl, unsigned *idx_args)
+{
+  tree attrs = lookup_attribute ("format", DECL_ATTRIBUTES (fndecl));
+  if (!attrs)
+    attrs = lookup_attribute ("format", TYPE_ATTRIBUTES (TREE_TYPE (fndecl)));
+
+  if (!attrs)
+    return UINT_MAX;
+
+  attrs = TREE_VALUE (attrs);
+
+  tree archetype = TREE_VALUE (attrs);
+  if (strcmp ("printf", IDENTIFIER_POINTER (archetype)))
+    return UINT_MAX;
+
+  attrs = TREE_CHAIN (attrs);
+  tree fmtarg = TREE_VALUE (attrs);
+
+  attrs = TREE_CHAIN (attrs);
+  tree elliparg = TREE_VALUE (attrs);
+
+  /* Attribute argument indices are 1-based but we use zero-based.  */
+  *idx_args = tree_to_uhwi (elliparg) - 1;
+  return tree_to_uhwi (fmtarg) - 1;
+}
+
 /* Determine if a GIMPLE CALL is to one of the sprintf-like built-in
    functions and if so, handle it.  Return true if the call is removed
    and gsi_next should not be performed in the caller.  */
 
 bool
-pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
+sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
 {
   call_info info = call_info ();
 
   info.callstmt = gsi_stmt (*gsi);
-  if (!gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
+  info.func = gimple_call_fndecl (info.callstmt);
+  if (!info.func)
     return false;
 
-  info.func = gimple_call_fndecl (info.callstmt);
-  info.fncode = DECL_FUNCTION_CODE (info.func);
+  /* Format string argument number (valid for all functions).  */
+  unsigned idx_format = UINT_MAX;
+  if (gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
+    info.fncode = DECL_FUNCTION_CODE (info.func);
+  else
+    {
+      unsigned idx_args;
+      idx_format = get_user_idx_format (info.func, &idx_args);
+      if (idx_format == UINT_MAX
+         || idx_format >= gimple_call_num_args (info.callstmt)
+         || idx_args > gimple_call_num_args (info.callstmt)
+         || !POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (info.callstmt,
+                                                         idx_format))))
+       return false;
+      info.fncode = BUILT_IN_NONE;
+      info.argidx = idx_args;
+    }
 
   /* The size of the destination as in snprintf(dest, size, ...).  */
   unsigned HOST_WIDE_INT dstsize = HOST_WIDE_INT_M1U;
@@ -3349,17 +3885,70 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
   /* 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;
+  /* Zero-based buffer size argument number (snprintf and vsnprintf).  */
+  unsigned idx_dstsize = UINT_MAX;
 
   /* Object size argument number (snprintf_chk and vsnprintf_chk).  */
-  unsigned HOST_WIDE_INT idx_objsize = HOST_WIDE_INT_M1U;
+  unsigned idx_objsize = UINT_MAX;
 
-  /* Format string argument number (valid for all functions).  */
-  unsigned idx_format;
+  /* Destinaton argument number (valid for sprintf functions only).  */
+  unsigned idx_dstptr = 0;
 
   switch (info.fncode)
     {
+    case BUILT_IN_NONE:
+      // User-defined function with attribute format (printf).
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_FPRINTF:
+      // Signature:
+      //   __builtin_fprintf (FILE*, format, ...)
+      idx_format = 1;
+      info.argidx = 2;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_FPRINTF_CHK:
+      // Signature:
+      //   __builtin_fprintf_chk (FILE*, ost, format, ...)
+      idx_format = 2;
+      info.argidx = 3;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_FPRINTF_UNLOCKED:
+      // Signature:
+      //   __builtin_fprintf_unnlocked (FILE*, format, ...)
+      idx_format = 1;
+      info.argidx = 2;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_PRINTF:
+      // Signature:
+      //   __builtin_printf (format, ...)
+      idx_format = 0;
+      info.argidx = 1;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_PRINTF_CHK:
+      // Signature:
+      //   __builtin_printf_chk (ost, format, ...)
+      idx_format = 1;
+      info.argidx = 2;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_PRINTF_UNLOCKED:
+      // Signature:
+      //   __builtin_printf (format, ...)
+      idx_format = 0;
+      info.argidx = 1;
+      idx_dstptr = -1;
+      break;
+
     case BUILT_IN_SPRINTF:
       // Signature:
       //   __builtin_sprintf (dst, format, ...)
@@ -3394,6 +3983,38 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
       info.bounded = true;
       break;
 
+    case BUILT_IN_VFPRINTF:
+      // Signature:
+      //   __builtin_vprintf (FILE*, format, va_list)
+      idx_format = 1;
+      info.argidx = -1;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_VFPRINTF_CHK:
+      // Signature:
+      //   __builtin___vfprintf_chk (FILE*, ost, format, va_list)
+      idx_format = 2;
+      info.argidx = -1;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_VPRINTF:
+      // Signature:
+      //   __builtin_vprintf (format, va_list)
+      idx_format = 0;
+      info.argidx = -1;
+      idx_dstptr = -1;
+      break;
+
+    case BUILT_IN_VPRINTF_CHK:
+      // Signature:
+      //   __builtin___vprintf_chk (ost, format, va_list)
+      idx_format = 1;
+      info.argidx = -1;
+      idx_dstptr = -1;
+      break;
+
     case BUILT_IN_VSNPRINTF:
       // Signature:
       //   __builtin_vsprintf (dst, size, format, va)
@@ -3435,16 +4056,19 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
   /* Set the global warning level for this function.  */
   warn_level = info.bounded ? warn_format_trunc : warn_format_overflow;
 
-  /* The first argument is a pointer to the destination.  */
-  tree dstptr = gimple_call_arg (info.callstmt, 0);
+  /* For all string functions the first argument is a pointer to
+     the destination.  */
+  tree dstptr = (idx_dstptr < gimple_call_num_args (info.callstmt)
+                ? gimple_call_arg (info.callstmt, 0) : NULL_TREE);
 
   info.format = gimple_call_arg (info.callstmt, idx_format);
 
   /* True when the destination size is constant as opposed to the lower
      or upper bound of a range.  */
   bool dstsize_cst_p = true;
+  bool posunder4k = true;
 
-  if (idx_dstsize == HOST_WIDE_INT_M1U)
+  if (idx_dstsize == UINT_MAX)
     {
       /* For non-bounded functions like sprintf, determine the size
         of the destination from the object or pointer passed to it
@@ -3469,32 +4093,56 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
              /* Avoid warning if -Wstringop-overflow is specified since
                 it also warns for the same thing though only for the
                 checking built-ins.  */
-             if ((idx_objsize == HOST_WIDE_INT_M1U
+             if ((idx_objsize == UINT_MAX
                   || !warn_stringop_overflow))
                warning_at (gimple_location (info.callstmt), info.warnopt (),
                            "specified bound %wu exceeds maximum object size "
                            "%wu",
                            dstsize, target_size_max () / 2);
+             /* POSIX requires snprintf to fail if DSTSIZE is greater
+                than INT_MAX.  Even though not all POSIX implementations
+                conform to the requirement, avoid folding in this case.  */
+             posunder4k = false;
            }
          else if (dstsize > target_int_max ())
-           warning_at (gimple_location (info.callstmt), info.warnopt (),
-                       "specified bound %wu exceeds %<INT_MAX %>",
-                       dstsize);
+           {
+             warning_at (gimple_location (info.callstmt), info.warnopt (),
+                         "specified bound %wu exceeds %<INT_MAX%>",
+                         dstsize);
+             /* POSIX requires snprintf to fail if DSTSIZE is greater
+                than INT_MAX.  Avoid folding in that case.  */
+             posunder4k = false;
+           }
        }
       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 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)
+         const value_range *vr = evrp_range_analyzer.get_value_range (size);
+         if (range_int_cst_p (vr))
            {
-             dstsize
-               = (warn_level < 2
-                  ? wi::fits_uhwi_p (max) ? max.to_uhwi () : max.to_shwi ()
-                  : wi::fits_uhwi_p (min) ? min.to_uhwi () : min.to_shwi ());
+             unsigned HOST_WIDE_INT minsize = TREE_INT_CST_LOW (vr->min ());
+             unsigned HOST_WIDE_INT maxsize = TREE_INT_CST_LOW (vr->max ());
+             dstsize = warn_level < 2 ? maxsize : minsize;
+
+             if (minsize > target_int_max ())
+               warning_at (gimple_location (info.callstmt), info.warnopt (),
+                           "specified bound range [%wu, %wu] exceeds "
+                           "%<INT_MAX%>",
+                           minsize, maxsize);
+
+             /* POSIX requires snprintf to fail if DSTSIZE is greater
+                than INT_MAX.  Avoid folding if that's possible.  */
+             if (maxsize > target_int_max ())
+               posunder4k = false;
+           }
+         else if (vr->varying_p ())
+           {
+             /* POSIX requires snprintf to fail if DSTSIZE is greater
+                than INT_MAX.  Since SIZE's range is unknown, avoid
+                folding.  */
+             posunder4k = false;
            }
 
          /* The destination size is not constant.  If the function is
@@ -3504,7 +4152,7 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
        }
     }
 
-  if (idx_objsize != HOST_WIDE_INT_M1U)
+  if (idx_objsize != UINT_MAX)
     if (tree size = gimple_call_arg (info.callstmt, idx_objsize))
       if (tree_fits_uhwi_p (size))
        objsize = tree_to_uhwi (size);
@@ -3524,14 +4172,15 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
       /* For calls to non-bounded functions or to those of bounded
         functions with a non-zero size, warn if the destination
         pointer is null.  */
-      if (integer_zerop (dstptr))
+      if (dstptr && integer_zerop (dstptr))
        {
          /* This is diagnosed with -Wformat only when the null is a constant
             pointer.  The warning here diagnoses instances where the pointer
             is not constant.  */
          location_t loc = gimple_location (info.callstmt);
          warning_at (EXPR_LOC_OR_LOC (dstptr, loc),
-                     info.warnopt (), "null destination pointer");
+                     info.warnopt (), "%Gnull destination pointer",
+                     info.callstmt);
          return false;
        }
 
@@ -3544,7 +4193,7 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
          /* Avoid warning if -Wstringop-overflow is specified since
             it also warns for the same thing though only for the
             checking built-ins.  */
-         && (idx_objsize == HOST_WIDE_INT_M1U
+         && (idx_objsize == UINT_MAX
              || !warn_stringop_overflow))
        {
          warning_at (gimple_location (info.callstmt), info.warnopt (),
@@ -3553,14 +4202,15 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
        }
     }
 
-  if (integer_zerop (info.format))
+  /* Determine if the format argument may be null and warn if not
+     and if the argument is null.  */
+  if (integer_zerop (info.format)
+      && gimple_call_builtin_p (info.callstmt, BUILT_IN_NORMAL))
     {
-      /* This is diagnosed with -Wformat only when the null is a constant
-        pointer.  The warning here diagnoses instances where the pointer
-        is not constant.  */
       location_t loc = gimple_location (info.callstmt);
       warning_at (EXPR_LOC_OR_LOC (info.format, loc),
-                 info.warnopt (), "null format string");
+                 info.warnopt (), "%Gnull format string",
+                 info.callstmt);
       return false;
     }
 
@@ -3572,19 +4222,67 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
      including the terminating NUL.  */
   format_result res = format_result ();
 
+  /* I/O functions with no destination argument (i.e., all forms of fprintf
+     and printf) may fail under any conditions.  Others (i.e., all forms of
+     sprintf) may only fail under specific conditions determined for each
+     directive.  Clear POSUNDER4K for the former set of functions and set
+     it to true for the latter (it can only be cleared later, but it is
+     never set to true again).  */
+  res.posunder4k = posunder4k && dstptr;
+
   bool success = compute_format_length (info, &res);
+  if (res.warned)
+    gimple_set_no_warning (info.callstmt, true);
 
   /* 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 (success
-      && optimize > 0
-      && flag_printf_return_value
-      && (!flag_rounding_math || !res.floating))
-    return try_substitute_return_value (gsi, info, res);
+  bool call_removed = false;
+  if (success && optimize > 0)
+    {
+      /* Save a copy of the iterator pointing at the call.  The iterator
+        may change to point past the call in try_substitute_return_value
+        but the original value is needed in try_simplify_call.  */
+      gimple_stmt_iterator gsi_call = *gsi;
 
-  return false;
+      if (flag_printf_return_value
+         && (!flag_rounding_math || !res.floating))
+       call_removed = try_substitute_return_value (gsi, info, res);
+
+      if (!call_removed)
+       try_simplify_call (&gsi_call, info, res);
+    }
+
+  return call_removed;
+}
+
+edge
+sprintf_dom_walker::before_dom_children (basic_block bb)
+{
+  evrp_range_analyzer.enter (bb);
+  for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si); )
+    {
+      /* Iterate over statements, looking for function calls.  */
+      gimple *stmt = gsi_stmt (si);
+
+      /* First record ranges generated by this statement.  */
+      evrp_range_analyzer.record_ranges_from_stmt (stmt, false);
+
+      if (is_gimple_call (stmt) && handle_gimple_call (&si))
+       /* If handle_gimple_call returns true, the iterator is
+          already pointing to the next statement.  */
+       continue;
+
+      gsi_next (&si);
+    }
+  return NULL;
+}
+
+void
+sprintf_dom_walker::after_dom_children (basic_block bb)
+{
+  evrp_range_analyzer.leave (bb);
 }
 
 /* Execute the pass for function FUN.  */
@@ -3592,26 +4290,27 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
 unsigned int
 pass_sprintf_length::execute (function *fun)
 {
-  basic_block bb;
-  FOR_EACH_BB_FN (bb, fun)
+  init_target_to_host_charmap ();
+
+  calculate_dominance_info (CDI_DOMINATORS);
+  bool use_scev = optimize > 0 && flag_printf_return_value;
+  if (use_scev)
     {
-      for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si); )
-       {
-         /* Iterate over statements, looking for function calls.  */
-         gimple *stmt = gsi_stmt (si);
+      loop_optimizer_init (LOOPS_NORMAL);
+      scev_initialize ();
+    }
 
-         if (is_gimple_call (stmt) && handle_gimple_call (&si))
-           /* If handle_gimple_call returns true, the iterator is
-              already pointing to the next statement.  */
-           continue;
+  sprintf_dom_walker sprintf_dom_walker;
+  sprintf_dom_walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun));
 
-         gsi_next (&si);
-       }
+  if (use_scev)
+    {
+      scev_finalize ();
+      loop_optimizer_finalize ();
     }
 
   /* Clean up object size info.  */
   fini_object_sizes ();
-
   return 0;
 }