PR fortran/95980 - ICE in get_unique_type_string, at fortran/class.c:485
[gcc.git] / gcc / gimple-ssa-sprintf.c
index 35ceb2cfb75914d26f4245ce25cda0205c4edd8a..011c3e21e6359b90a5627688680ae38f9f091800 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2016-2017 Free Software Foundation, Inc.
+/* Copyright (C) 2016-2020 Free Software Foundation, Inc.
    Contributed by Martin Sebor <msebor@redhat.com>.
 
 This file is part of GCC.
@@ -60,14 +60,16 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimple-iterator.h"
 #include "tree-ssa.h"
 #include "tree-object-size.h"
-#include "params.h"
 #include "tree-cfg.h"
 #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"
 
@@ -80,6 +82,10 @@ along with GCC; see the file COPYING3.  If not see
 #include "substring-locations.h"
 #include "diagnostic.h"
 #include "domwalk.h"
+#include "alloc-pool.h"
+#include "vr-values.h"
+#include "tree-ssa-strlen.h"
+#include "tree-dfa.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
@@ -94,76 +100,12 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace {
 
-const pass_data pass_data_sprintf_length = {
-  GIMPLE_PASS,             // pass type
-  "printf-return-value",   // pass name
-  OPTGROUP_NONE,           // optinfo_flags
-  TV_NONE,                 // tv_id
-  PROP_cfg,                // properties_required
-  0,                      // properties_provided
-  0,                      // properties_destroyed
-  0,                      // properties_start
-  0,                      // properties_finish
-};
-
 /* Set to the warning level for the current function which is equal
    either to warn_format_trunc for bounded functions or to
    warn_format_overflow otherwise.  */
 
 static int warn_level;
 
-struct format_result;
-
-class sprintf_dom_walker : public dom_walker
-{
- public:
-  sprintf_dom_walker () : dom_walker (CDI_DOMINATORS) {}
-  ~sprintf_dom_walker () {}
-
-  edge before_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 pass_sprintf_length : public gimple_opt_pass
-{
-  bool fold_return_value;
-
-public:
-  pass_sprintf_length (gcc::context *ctxt)
-    : gimple_opt_pass (pass_data_sprintf_length, ctxt),
-    fold_return_value (false)
-  { }
-
-  opt_pass * clone () { return new pass_sprintf_length (m_ctxt); }
-
-  virtual bool gate (function *);
-
-  virtual unsigned int execute (function *);
-
-  void set_pass_param (unsigned int n, bool param)
-    {
-      gcc_assert (n == 0);
-      fold_return_value = param;
-    }
-
-};
-
-bool
-pass_sprintf_length::gate (function *)
-{
-  /* Run the pass iff -Warn-format-overflow or -Warn-format-truncation
-     is specified and either not optimizing and the pass is being invoked
-     early, or when optimizing and the pass is being invoked during
-     optimization (i.e., "late").  */
-  return ((warn_format_overflow > 0
-          || warn_format_trunc > 0
-          || flag_printf_return_value)
-         && (optimize > 0) == fold_return_value);
-}
-
 /* The minimum, maximum, likely, and unlikely maximum number of bytes
    of output either a formatting function or an individual directive
    can result in.  */
@@ -188,78 +130,6 @@ struct result_range
   unsigned HOST_WIDE_INT unlikely;
 };
 
-/* The result of a call to a formatted function.  */
-
-struct format_result
-{
-  /* Range of characters written by the formatted function.
-     Setting the minimum to HOST_WIDE_INT_MAX disables all
-     length tracking for the remainder of the format string.  */
-  result_range range;
-
-  /* True when the range above is obtained from known values of
-     directive arguments, or bounds on the amount of output such
-     as width and precision, and not the result of  heuristics that
-     depend on warning levels.  It's used to issue stricter diagnostics
-     in cases where strings of unknown lengths are bounded by the arrays
-     they are determined to refer to.  KNOWNRANGE must not be used for
-     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 when a floating point directive has been seen in the format
-     string.  */
-  bool floating;
-
-  /* True when an intermediate result has caused a warning.  Used to
-     avoid issuing duplicate warnings while finishing the processing
-     of a call.  WARNED also disables the return value optimization.  */
-  bool warned;
-
-  /* Preincrement the number of output characters by 1.  */
-  format_result& operator++ ()
-  {
-    return *this += 1;
-  }
-
-  /* Postincrement the number of output characters by 1.  */
-  format_result operator++ (int)
-  {
-    format_result prev (*this);
-    *this += 1;
-    return prev;
-  }
-
-  /* Increment the number of output characters by N.  */
-  format_result& operator+= (unsigned HOST_WIDE_INT);
-};
-
-format_result&
-format_result::operator+= (unsigned HOST_WIDE_INT n)
-{
-  gcc_assert (n < HOST_WIDE_INT_MAX);
-
-  if (range.min < HOST_WIDE_INT_MAX)
-    range.min += n;
-
-  if (range.max < HOST_WIDE_INT_MAX)
-    range.max += n;
-
-  if (range.likely < HOST_WIDE_INT_MAX)
-    range.likely += n;
-
-  if (range.unlikely < HOST_WIDE_INT_MAX)
-    range.unlikely += n;
-
-  return *this;
-}
-
 /* Return the value of INT_MIN for the target.  */
 
 static inline HOST_WIDE_INT
@@ -371,9 +241,14 @@ target_to_host (char *hostr, size_t hostsz, const char *targstr)
      overlong strings just like the translated strings are.  */
   if (target_to_host_charmap['\0'] == 1)
     {
-      strncpy (hostr, targstr, hostsz - 4);
-      if (strlen (targstr) >= hostsz)
-       strcpy (hostr + hostsz - 4, "...");
+      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;
     }
 
@@ -387,10 +262,9 @@ target_to_host (char *hostr, size_t hostsz, const char *targstr)
       if (!*targstr)
        break;
 
-      if (size_t (ph - hostr) == hostsz - 4)
+      if (size_t (ph - hostr) == hostsz)
        {
-         *ph = '\0';
-         strcat (ph, "...");
+         strcpy (ph - 4, "...");
          break;
        }
     }
@@ -399,12 +273,12 @@ target_to_host (char *hostr, size_t hostsz, const char *targstr)
 }
 
 /* Convert the sequence of decimal digits in the execution character
-   starting at S to a long, just like strtol does.  Return the result
-   and set *END to one past the last converted character.  On range
-   error set ERANGE to the digit that caused it.  */
+   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.  */
 
-static inline long
-target_strtol10 (const char **ps, const char **erange)
+static inline HOST_WIDE_INT
+target_strtowi (const char **ps, const char **erange)
 {
   unsigned HOST_WIDE_INT val = 0;
   for ( ; ; ++*ps)
@@ -415,9 +289,9 @@ target_strtol10 (const char **ps, const char **erange)
          c -= '0';
 
          /* Check for overflow.  */
-         if (val > (LONG_MAX - c) / 10LU)
+         if (val > ((unsigned HOST_WIDE_INT) HOST_WIDE_INT_MAX - c) / 10LU)
            {
-             val = LONG_MAX;
+             val = HOST_WIDE_INT_MAX;
              *erange = *ps;
 
              /* Skip the remaining digits.  */
@@ -436,166 +310,54 @@ target_strtol10 (const char **ps, const char **erange)
   return val;
 }
 
-/* Return the constant initial value of DECL if available or DECL
-   otherwise.  Same as the synonymous function in c/c-typeck.c.  */
-
-static tree
-decl_constant_value (tree decl)
-{
-  if (/* Don't change a variable array bound or initial value to a constant
-        in a place where a variable is invalid.  Note that DECL_INITIAL
-        isn't valid for a PARM_DECL.  */
-      current_function_decl != 0
-      && TREE_CODE (decl) != PARM_DECL
-      && !TREE_THIS_VOLATILE (decl)
-      && TREE_READONLY (decl)
-      && DECL_INITIAL (decl) != 0
-      && TREE_CODE (DECL_INITIAL (decl)) != ERROR_MARK
-      /* This is invalid if initial value is not constant.
-        If it has either a function call, a memory reference,
-        or a variable, then re-evaluating it could give different results.  */
-      && TREE_CONSTANT (DECL_INITIAL (decl))
-      /* Check for cases where this is sub-optimal, even though valid.  */
-      && TREE_CODE (DECL_INITIAL (decl)) != CONSTRUCTOR)
-    return DECL_INITIAL (decl);
-  return decl;
-}
-
 /* Given FORMAT, set *PLOC to the source location of the format string
    and return the format string if it is known or null otherwise.  */
 
 static const char*
 get_format_string (tree format, location_t *ploc)
 {
-  if (VAR_P (format))
-    {
-      /* Pull out a constant value if the front end didn't.  */
-      format = decl_constant_value (format);
-      STRIP_NOPS (format);
-    }
-
-  if (integer_zerop (format))
-    {
-      /* FIXME: Diagnose null format string if it hasn't been diagnosed
-        by -Wformat (the latter diagnoses only nul pointer constants,
-        this pass can do better).  */
-      return NULL;
-    }
-
-  HOST_WIDE_INT offset = 0;
-
-  if (TREE_CODE (format) == POINTER_PLUS_EXPR)
-    {
-      tree arg0 = TREE_OPERAND (format, 0);
-      tree arg1 = TREE_OPERAND (format, 1);
-      STRIP_NOPS (arg0);
-      STRIP_NOPS (arg1);
-
-      if (TREE_CODE (arg1) != INTEGER_CST)
-       return NULL;
-
-      format = arg0;
-
-      /* POINTER_PLUS_EXPR offsets are to be interpreted signed.  */
-      if (!cst_and_fits_in_hwi (arg1))
-       return NULL;
-
-      offset = int_cst_value (arg1);
-    }
-
-  if (TREE_CODE (format) != ADDR_EXPR)
-    return NULL;
-
   *ploc = EXPR_LOC_OR_LOC (format, input_location);
 
-  format = TREE_OPERAND (format, 0);
-
-  if (TREE_CODE (format) == ARRAY_REF
-      && tree_fits_shwi_p (TREE_OPERAND (format, 1))
-      && (offset += tree_to_shwi (TREE_OPERAND (format, 1))) >= 0)
-    format = TREE_OPERAND (format, 0);
-
-  if (offset < 0)
-    return NULL;
-
-  tree array_init;
-  tree array_size = NULL_TREE;
-
-  if (VAR_P (format)
-      && TREE_CODE (TREE_TYPE (format)) == ARRAY_TYPE
-      && (array_init = decl_constant_value (format)) != format
-      && TREE_CODE (array_init) == STRING_CST)
-    {
-      /* Extract the string constant initializer.  Note that this may
-        include a trailing NUL character that is not in the array (e.g.
-        const char a[3] = "foo";).  */
-      array_size = DECL_SIZE_UNIT (format);
-      format = array_init;
-    }
-
-  if (TREE_CODE (format) != STRING_CST)
-    return NULL;
-
-  tree type = TREE_TYPE (format);
-
-  scalar_int_mode char_mode;
-  if (!is_int_mode (TYPE_MODE (TREE_TYPE (type)), &char_mode)
-      || GET_MODE_SIZE (char_mode) != 1)
-    {
-      /* Wide format string.  */
-      return NULL;
-    }
-
-  const char *fmtstr = TREE_STRING_POINTER (format);
-  unsigned fmtlen = TREE_STRING_LENGTH (format);
-
-  if (array_size)
-    {
-      /* Variable length arrays can't be initialized.  */
-      gcc_assert (TREE_CODE (array_size) == INTEGER_CST);
-
-      if (tree_fits_shwi_p (array_size))
-       {
-         HOST_WIDE_INT array_size_value = tree_to_shwi (array_size);
-         if (array_size_value > 0
-             && array_size_value == (int) array_size_value
-             && fmtlen > array_size_value)
-           fmtlen = array_size_value;
-       }
-    }
-  if (offset)
-    {
-      if (offset >= fmtlen)
-       return NULL;
-
-      fmtstr += offset;
-      fmtlen -= offset;
-    }
-
-  if (fmtlen < 1 || fmtstr[--fmtlen] != 0)
-    {
-      /* FIXME: Diagnose an unterminated format string if it hasn't been
-        diagnosed by -Wformat.  Similarly to a null format pointer,
-        -Wformay diagnoses only nul pointer constants, this pass can
-        do better).  */
-      return NULL;
-    }
-
-  return fmtstr;
+  return c_getstr (format);
 }
 
-/* The format_warning_at_substring function is not used here in a way
-   that makes using attribute format viable.  Suppress the warning.  */
+/* 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.  */
 
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
-
-/* 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 &, location_t,
-                    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.  */
 
@@ -616,14 +378,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 (), dst_offset (HOST_WIDE_INT_MIN), nonstr (),
     knownrange (min < HOST_WIDE_INT_MAX),
-    nullp ()
+    mayfail (), nullp ()
   {
     range.min = min;
     range.max = min;
@@ -635,9 +398,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 (), dst_offset (HOST_WIDE_INT_MIN), nonstr (),
     knownrange (min < HOST_WIDE_INT_MAX && max < HOST_WIDE_INT_MAX),
-    nullp ()
+    mayfail (), nullp ()
   {
     range.min = min;
     range.max = max;
@@ -658,15 +421,28 @@ struct fmtresult
   /* The range a directive's argument is in.  */
   tree argmin, argmax;
 
+  /* The starting offset into the destination of the formatted function
+     call of the %s argument that points into (aliases with) the same
+     destination array.  */
+  HOST_WIDE_INT dst_offset;
+
   /* The minimum and maximum number of bytes that a directive
      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;
 };
@@ -754,28 +530,48 @@ 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, HOST_WIDE_INT *, HOST_WIDE_INT *, bool, HOST_WIDE_INT);
+get_int_range (tree, HOST_WIDE_INT *, HOST_WIDE_INT *, bool, HOST_WIDE_INT,
+              const vr_values *);
+
+struct call_info;
 
 /* Description of a format directive.  A directive is either a plain
    string or a conversion specification that starts with '%'.  */
 
 struct directive
 {
+  directive (const call_info *inf, unsigned dno)
+    : info (inf), dirno (dno), argno (), beg (), len (), flags (),
+    width (), prec (),  modifier (), specifier (), arg (), fmtfunc ()
+  { }
+
+  /* Reference to the info structure describing the call that this
+     directive is a part of.  */
+  const call_info *info;
+
   /* The 1-based directive number (for debugging).  */
   unsigned dirno;
 
+  /* The zero-based argument number of the directive's argument ARG in
+     the function's argument list.  */
+  unsigned argno;
+
   /* The first character of the directive and its length.  */
   const char *beg;
   size_t len;
@@ -801,7 +597,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, const vr_values *);
 
   /* Return True when a the format flag CHR has been used.  */
   bool get_flag (char chr) const
@@ -838,9 +634,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, const vr_values *vr)
   {
-    get_int_range (arg, width, width + 1, true, 0);
+    get_int_range (arg, width, width + 1, true, 0, vr);
   }
 
   /* Set both bounds of the precision range to VAL.  */
@@ -854,9 +650,9 @@ 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, const vr_values *vr)
   {
-    get_int_range (arg, prec, prec + 1, false, -1);
+    get_int_range (arg, prec, prec + 1, false, -1, vr);
   }
 
   /* Return true if both width and precision are known to be
@@ -870,6 +666,130 @@ struct directive
   }
 };
 
+/* The result of a call to a formatted function.  */
+
+struct format_result
+{
+  format_result ()
+    : range (), aliases (), alias_count (), knownrange (), posunder4k (),
+    floating (), warned () { /* No-op.  */ }
+
+  ~format_result ()
+  {
+    XDELETEVEC (aliases);
+  }
+
+  /* Range of characters written by the formatted function.
+     Setting the minimum to HOST_WIDE_INT_MAX disables all
+     length tracking for the remainder of the format string.  */
+  result_range range;
+
+  struct alias_info
+  {
+    directive dir;          /* The directive that aliases the destination.  */
+    HOST_WIDE_INT offset;   /* The offset at which it aliases it.  */
+    result_range range;     /* The raw result of the directive.  */
+  };
+
+  /* An array of directives whose pointer argument aliases a part
+     of the destination object of the formatted function.  */
+  alias_info *aliases;
+  unsigned alias_count;
+
+  /* True when the range above is obtained from known values of
+     directive arguments, or bounds on the amount of output such
+     as width and precision, and not the result of  heuristics that
+     depend on warning levels.  It's used to issue stricter diagnostics
+     in cases where strings of unknown lengths are bounded by the arrays
+     they are determined to refer to.  KNOWNRANGE must not be used for
+     the return value optimization.  */
+  bool knownrange;
+
+  /* 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.  */
+  bool floating;
+
+  /* True when an intermediate result has caused a warning.  Used to
+     avoid issuing duplicate warnings while finishing the processing
+     of a call.  WARNED also disables the return value optimization.  */
+  bool warned;
+
+  /* Preincrement the number of output characters by 1.  */
+  format_result& operator++ ()
+  {
+    return *this += 1;
+  }
+
+  /* Postincrement the number of output characters by 1.  */
+  format_result operator++ (int)
+  {
+    format_result prev (*this);
+    *this += 1;
+    return prev;
+  }
+
+  /* Increment the number of output characters by N.  */
+  format_result& operator+= (unsigned HOST_WIDE_INT);
+
+  /* Add a directive to the sequence of those with potentially aliasing
+     arguments.  */
+  void append_alias (const directive &, HOST_WIDE_INT, const result_range &);
+
+private:
+  /* Not copyable or assignable.  */
+  format_result (format_result&);
+  void operator= (format_result&);
+};
+
+format_result&
+format_result::operator+= (unsigned HOST_WIDE_INT n)
+{
+  gcc_assert (n < HOST_WIDE_INT_MAX);
+
+  if (range.min < HOST_WIDE_INT_MAX)
+    range.min += n;
+
+  if (range.max < HOST_WIDE_INT_MAX)
+    range.max += n;
+
+  if (range.likely < HOST_WIDE_INT_MAX)
+    range.likely += n;
+
+  if (range.unlikely < HOST_WIDE_INT_MAX)
+    range.unlikely += n;
+
+  return *this;
+}
+
+void
+format_result::append_alias (const directive &d, HOST_WIDE_INT off,
+                            const result_range &resrng)
+{
+  unsigned cnt = alias_count + 1;
+  alias_info *ar = XNEWVEC (alias_info, cnt);
+
+  for (unsigned i = 0; i != alias_count; ++i)
+    ar[i] = aliases[i];
+
+  ar[alias_count].dir = d;
+  ar[alias_count].offset = off;
+  ar[alias_count].range = resrng;
+
+  XDELETEVEC (aliases);
+
+  alias_count = cnt;
+  aliases = ar;
+}
+
 /* Return the logarithm of X in BASE.  */
 
 static int
@@ -951,42 +871,9 @@ tree_digits (tree x, int base, HOST_WIDE_INT prec, bool plus, bool prefix)
   return res;
 }
 
-/* Given the formatting result described by RES and NAVAIL, the number
-   of available in the destination, return the range of bytes remaining
-   in the destination.  */
-
-static inline result_range
-bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
-{
-  result_range range;
-
-  if (HOST_WIDE_INT_MAX <= navail)
-    {
-      range.min = range.max = range.likely = range.unlikely = navail;
-      return range;
-    }
-
-  /* The lower bound of the available range is the available size
-     minus the maximum output size, and the upper bound is the size
-     minus the minimum.  */
-  range.max = res.range.min < navail ? navail - res.range.min : 0;
-
-  range.likely = res.range.likely < navail ? navail - res.range.likely : 0;
-
-  if (res.range.max < HOST_WIDE_INT_MAX)
-    range.min = res.range.max < navail ? navail - res.range.max : 0;
-  else
-    range.min = range.likely;
-
-  range.unlikely = (res.range.unlikely < navail
-                   ? navail - res.range.unlikely : 0);
-
-  return range;
-}
-
 /* Description of a call to a formatted function.  */
 
-struct sprintf_dom_walker::call_info
+struct call_info
 {
   /* Function call statement.  */
   gimple *callstmt;
@@ -997,6 +884,18 @@ struct sprintf_dom_walker::call_info
   /* Called built-in function code.  */
   built_in_function fncode;
 
+  /* The "origin" of the destination pointer argument, which is either
+     the DECL of the destination buffer being written into or a pointer
+     that points to it, plus some offset.  */
+  tree dst_origin;
+
+  /* For a destination pointing to a struct array member, the offset of
+     the member.  */
+  HOST_WIDE_INT dst_field;
+
+  /* The offset into the destination buffer.  */
+  HOST_WIDE_INT dst_offset;
+
   /* Format argument and format string extracted from it.  */
   tree format;
   const char *fmtstr;
@@ -1032,12 +931,35 @@ struct sprintf_dom_walker::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, const vr_values *)
 {
   fmtresult res (0);
   return res;
@@ -1046,7 +968,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, const vr_values *)
 {
   fmtresult res (1);
   return res;
@@ -1079,10 +1001,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;
@@ -1103,7 +1027,8 @@ build_intmax_type_nodes (tree *pintmax, tree *puintmax)
 
 static bool
 get_int_range (tree arg, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
-              bool absolute, HOST_WIDE_INT negbound)
+              bool absolute, HOST_WIDE_INT negbound,
+              const class vr_values *vr_values)
 {
   /* The type of the result.  */
   const_tree type = integer_type_node;
@@ -1142,9 +1067,10 @@ get_int_range (tree arg, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
          && 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_equiv *vr
+           = CONST_CAST (class vr_values *, vr_values)->get_value_range (arg);
+
+         if (range_int_cst_p (vr))
            {
              HOST_WIDE_INT type_min
                = (TYPE_UNSIGNED (argtype)
@@ -1153,8 +1079,8 @@ get_int_range (tree arg, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
 
              HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (argtype));
 
-             *pmin = min.to_shwi ();
-             *pmax = max.to_shwi ();
+             *pmin = TREE_INT_CST_LOW (vr->min ());
+             *pmax = TREE_INT_CST_LOW (vr->max ());
 
              if (*pmin < *pmax)
                {
@@ -1174,7 +1100,8 @@ get_int_range (tree arg, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
       /* Handle an argument with an unknown range as if none had been
         provided.  */
       if (unknown)
-       return get_int_range (NULL_TREE, 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.  */
@@ -1259,7 +1186,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, const vr_values *vr_values)
 {
   tree intmax_type_node;
   tree uintmax_type_node;
@@ -1442,12 +1369,13 @@ format_integer (const directive &dir, tree arg)
     {
       /* 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_equiv *vr
+       = CONST_CAST (class vr_values *, 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 and neither width nor precision
@@ -1460,11 +1388,11 @@ format_integer (const directive &dir, tree arg)
          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
@@ -1477,7 +1405,7 @@ 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)
@@ -1522,16 +1450,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
     {
@@ -1539,9 +1467,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);
     }
 
@@ -1675,7 +1606,7 @@ format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
      round-to-nearest mode.  */
   mpfr_t x;
   mpfr_init2 (x, rfmt->p);
-  mpfr_from_real (x, &rv, GMP_RNDN);
+  mpfr_from_real (x, &rv, MPFR_RNDN);
 
   /* Return a value one greater to account for the leading minus sign.  */
   unsigned HOST_WIDE_INT r
@@ -1723,6 +1654,11 @@ format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
   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
      otherwise depends on the specification and precision.  */
@@ -1739,14 +1675,13 @@ format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
        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', prec[1]);
-       res.range.likely = res.range.min;
 
        /* The unlikely maximum accounts for the longest multibyte
           decimal point character.  */
@@ -1764,15 +1699,14 @@ format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
           non-zero, decimal point.  */
        HOST_WIDE_INT minprec = prec[0] ? prec[0] + !radix : 0;
 
-       /* The minimum output is "[-+]1.234567e+00" regardless
+       /* The likely minimum output is "[-+]1.234567e+00" regardless
           of the value of the actual argument.  */
-       res.range.min = (flagmin
-                        + radix
-                        + minprec
-                        + 2 /* e+ */ + 2);
+       res.range.likely = (flagmin
+                           + radix
+                           + minprec
+                           + 2 /* e+ */ + 2);
 
        res.range.max = format_floating_max (type, 'e', prec[1]);
-       res.range.likely = res.range.min;
 
        /* The unlikely maximum accounts for the longest multibyte
           decimal point character.  */
@@ -1791,12 +1725,15 @@ format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
           decimal point.  */
        HOST_WIDE_INT minprec = prec[0] ? prec[0] + !radix : 0;
 
-       /* 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).  */
-       res.range.min = flagmin + radix + minprec;
+       /* 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', prec[1]);
@@ -1806,7 +1743,7 @@ format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
        if (dir.prec[0] < 0 && dir.prec[1] > 0)
          res.range.likely = 3;
        else
-         res.range.likely = res.range.min;
+         res.range.likely = min;
 
        /* The unlikely maximum accounts for the longest multibyte
           decimal point character.  */
@@ -1824,7 +1761,9 @@ format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
           the lower bound on the range of bytes (not counting flags
           or width) is 1 plus radix (i.e., either "0" or "0." for
           "%g" and "%#g", respectively, with a zero argument).  */
-       res.range.min = flagmin + radix;
+       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];
@@ -1882,9 +1821,11 @@ format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
    ARG.  */
 
 static fmtresult
-format_floating (const directive &dir, tree arg)
+format_floating (const directive &dir, tree arg, const vr_values *)
 {
   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.  */
@@ -1892,10 +1833,6 @@ format_floating (const directive &dir, tree arg)
     {
       /* Get the number of fractional decimal digits needed to represent
         the argument without a loss of accuracy.  */
-      tree type = arg ? TREE_TYPE (arg) :
-       (dir.modifier == FMT_LEN_L || dir.modifier == FMT_LEN_ll
-        ? long_double_type_node : double_type_node);
-
       unsigned fmtprec
        = REAL_MODE_FORMAT (TYPE_MODE (type))->p;
 
@@ -1946,7 +1883,9 @@ format_floating (const directive &dir, tree arg)
        }
     }
 
-  if (!arg || TREE_CODE (arg) != REAL_CST)
+  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.  */
@@ -1956,6 +1895,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;
 
@@ -1981,7 +1953,7 @@ format_floating (const directive &dir, tree arg)
           rounding in either direction can result in longer output.  */
        mpfr_t mpfrval;
        mpfr_init2 (mpfrval, rfmt->p);
-       mpfr_from_real (mpfrval, rvp, i ? GMP_RNDU : GMP_RNDD);
+       mpfr_from_real (mpfrval, rvp, i ? MPFR_RNDU : MPFR_RNDD);
 
        /* Use the MPFR rounding specifier to round down in the first
           iteration and then up.  In most but not all cases this will
@@ -2043,77 +2015,106 @@ 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, const vr_values *vr)
 {
   if (!str)
     return fmtresult ();
 
-  if (tree slen = c_strlen (str, 1))
+  /* Try to determine the dynamic string length first.
+     Set MAXBOUND to an arbitrary non-null non-integer node as a request
+     to have it set to the length of the longest string in a PHI.  */
+  c_strlen_data lendata = { };
+  lendata.maxbound = str;
+  if (eltsize == 1)
+    get_range_strlen_dynamic (str, &lendata, vr);
+  else
     {
-      /* 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 LENDATA.MAXLEN
+        set to SIZE_MAX.  */
+      get_range_strlen (str, &lendata, eltsize);
     }
 
-  /* Determine the length of the shortest and longest string referenced
-     by STR.  Strings of unknown lengths are bounded by the sizes of
-     arrays that subexpressions of STR may refer to.  Pointers that
-     aren't known to point any such arrays result in LENRANGE[1] set
-     to SIZE_MAX.  */
-  tree lenrange[2];
-  bool flexarray = get_range_strlen (str, lenrange);
+  /* If LENDATA.MAXBOUND is not equal to .MINLEN it corresponds to the bound
+     of the largest array STR refers to, if known, or it's set to SIZE_MAX
+     otherwise.  */
 
-  if (lenrange [0] || lenrange [1])
+  /* Return the default result when nothing is known about the string.  */
+  if ((lendata.maxbound && !tree_fits_uhwi_p (lendata.maxbound))
+      || !tree_fits_uhwi_p (lendata.maxlen))
     {
-      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;
+      fmtresult res;
+      res.nonstr = lendata.decl;
+      return res;
+    }
 
+  unsigned HOST_WIDE_INT lenmax = tree_to_uhwi (max_object_size ()) - 2;
+  if (integer_zerop (lendata.minlen)
+      && (!lendata.maxbound || lenmax <= tree_to_uhwi (lendata.maxbound))
+      && lenmax <= tree_to_uhwi (lendata.maxlen))
+    {
+      fmtresult res;
+      res.nonstr = lendata.decl;
       return res;
     }
 
-  return get_string_length (NULL_TREE);
+  HOST_WIDE_INT min
+    = (tree_fits_uhwi_p (lendata.minlen)
+       ? tree_to_uhwi (lendata.minlen)
+       : 0);
+
+  HOST_WIDE_INT max
+    = (lendata.maxbound && 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 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.  If MAXBOUND is
+        known, also reset the length of the lower bound to zero.  */
+      res.range.likely = res.range.min ? res.range.min : warn_level > 1;
+      if (lendata.maxbound && !integer_all_onesp (lendata.maxbound))
+       res.range.min = 0;
+    }
+
+  res.range.unlikely = unbounded ? HOST_WIDE_INT_MAX : res.range.max;
+
+  return res;
 }
 
 /* Return the minimum and maximum number of characters formatted
@@ -2122,19 +2123,20 @@ get_string_length (tree str)
    vsprinf).  */
 
 static fmtresult
-format_character (const directive &dir, tree arg)
+format_character (const directive &dir, tree arg, const 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, &min, &max, false, 0))
+      if (get_int_range (arg, &min, &max, false, 0, vr_values))
        {
          if (min == 0 && max == 0)
            {
@@ -2143,13 +2145,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
            {
@@ -2158,6 +2167,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
@@ -2167,6 +2178,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
@@ -2181,18 +2193,277 @@ format_character (const directive &dir, tree arg)
   return res.adjust_for_width_or_precision (dir.width);
 }
 
+/* Determine the offset *INDEX of the first byte of an array element of
+   TYPE (possibly recursively) into which the byte offset OFF points.
+   On success set *INDEX to the offset of the first byte and return type.
+   Otherwise, if no such element can be found, return null.  */
+
+static tree
+array_elt_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
+{
+  gcc_assert (TREE_CODE (type) == ARRAY_TYPE);
+
+  tree eltype = type;
+  while (TREE_CODE (TREE_TYPE (eltype)) == ARRAY_TYPE)
+    eltype = TREE_TYPE (eltype);
+
+  if (TYPE_MODE (TREE_TYPE (eltype)) != TYPE_MODE (char_type_node))
+    eltype = TREE_TYPE (eltype);
+
+  if (eltype == type)
+    {
+      *index = 0;
+      return type;
+    }
+
+  HOST_WIDE_INT typsz = int_size_in_bytes (type);
+  HOST_WIDE_INT eltsz = int_size_in_bytes (eltype);
+  if (off < typsz * eltsz)
+    {
+      *index = (off / eltsz) * eltsz;
+      return TREE_CODE (eltype) == ARRAY_TYPE ? TREE_TYPE (eltype) : eltype;
+    }
+
+  return NULL_TREE;
+}
+
+/* Determine the offset *INDEX of the first byte of a struct member of TYPE
+   (possibly recursively) into which the byte offset OFF points.  On success
+   set *INDEX to the offset of the first byte and return true.  Otherwise,
+   if no such member can be found, return false.  */
+
+static bool
+field_at_offset (tree type, HOST_WIDE_INT off, HOST_WIDE_INT *index)
+{
+  gcc_assert (RECORD_OR_UNION_TYPE_P (type));
+
+  for (tree fld = TYPE_FIELDS (type); fld; fld = TREE_CHAIN (fld))
+    {
+      if (TREE_CODE (fld) != FIELD_DECL || DECL_ARTIFICIAL (fld))
+       continue;
+
+      tree fldtype = TREE_TYPE (fld);
+      HOST_WIDE_INT fldoff = int_byte_position (fld);
+
+      /* If the size is not available the field is a flexible array
+        member.  Treat this case as success.  */
+      tree typesize = TYPE_SIZE_UNIT (fldtype);
+      HOST_WIDE_INT fldsize = (tree_fits_uhwi_p (typesize)
+                              ? tree_to_uhwi (typesize)
+                              : off);
+
+      if (fldoff + fldsize < off)
+       continue;
+
+      if (TREE_CODE (fldtype) == ARRAY_TYPE)
+       {
+         HOST_WIDE_INT idx = 0;
+         if (tree ft = array_elt_at_offset (fldtype, off, &idx))
+           fldtype = ft;
+         else
+           break;
+
+         *index += idx;
+         fldoff -= idx;
+         off -= idx;
+       }
+
+      if (RECORD_OR_UNION_TYPE_P (fldtype))
+       {
+         *index += fldoff;
+         return field_at_offset (fldtype, off - fldoff, index);
+       }
+
+      *index += fldoff;
+      return true;
+    }
+
+  return false;
+}
+
+/* For an expression X of pointer type, recursively try to find the same
+   origin (object or pointer) as Y it references and return such an X.
+   When X refers to a struct member, set *FLDOFF to the offset of the
+   member from the beginning of the "most derived" object.  */
+
+static tree
+get_origin_and_offset (tree x, HOST_WIDE_INT *fldoff, HOST_WIDE_INT *off)
+{
+  if (!x)
+    return NULL_TREE;
+
+  switch (TREE_CODE (x))
+    {
+    case ADDR_EXPR:
+      x = TREE_OPERAND (x, 0);
+      return get_origin_and_offset (x, fldoff, off);
+
+    case ARRAY_REF:
+      {
+       tree offset = TREE_OPERAND (x, 1);
+       HOST_WIDE_INT idx = (tree_fits_uhwi_p (offset)
+                            ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+
+       tree eltype = TREE_TYPE (x);
+       if (TREE_CODE (eltype) == INTEGER_TYPE)
+         {
+           if (off)
+             *off = idx;
+         }
+       else if (idx < HOST_WIDE_INT_MAX)
+         *fldoff += idx * int_size_in_bytes (eltype);
+       else
+         *fldoff = idx;
+
+       x = TREE_OPERAND (x, 0);
+       return get_origin_and_offset (x, fldoff, NULL);
+      }
+
+    case MEM_REF:
+      if (off)
+       {
+         tree offset = TREE_OPERAND (x, 1);
+         *off = (tree_fits_uhwi_p (offset)
+                 ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+       }
+
+      x = TREE_OPERAND (x, 0);
+
+      if (off)
+       {
+         tree xtype
+           = (TREE_CODE (x) == ADDR_EXPR
+              ? TREE_TYPE (TREE_OPERAND (x, 0)) : TREE_TYPE (TREE_TYPE (x)));
+
+         /* The byte offset of the most basic struct member the byte
+            offset *OFF corresponds to, or for a (multidimensional)
+            array member, the byte offset of the array element.  */
+         HOST_WIDE_INT index = 0;
+
+         if ((RECORD_OR_UNION_TYPE_P (xtype)
+              && field_at_offset (xtype, *off, &index))
+             || (TREE_CODE (xtype) == ARRAY_TYPE
+                 && TREE_CODE (TREE_TYPE (xtype)) == ARRAY_TYPE
+                 && array_elt_at_offset (xtype, *off, &index)))
+           {
+             *fldoff += index;
+             *off -= index;
+           }
+       }
+
+      return get_origin_and_offset (x, fldoff, NULL);
+
+    case COMPONENT_REF:
+      {
+       tree fld = TREE_OPERAND (x, 1);
+       *fldoff += int_byte_position (fld);
+
+       get_origin_and_offset (fld, fldoff, off);
+       x = TREE_OPERAND (x, 0);
+       return get_origin_and_offset (x, fldoff, off);
+      }
+
+    case SSA_NAME:
+      {
+       gimple *def = SSA_NAME_DEF_STMT (x);
+       if (is_gimple_assign (def))
+         {
+           tree_code code = gimple_assign_rhs_code (def);
+           if (code == ADDR_EXPR)
+             {
+               x = gimple_assign_rhs1 (def);
+               return get_origin_and_offset (x, fldoff, off);
+             }
+
+           if (code == POINTER_PLUS_EXPR)
+             {
+               tree offset = gimple_assign_rhs2 (def);
+               if (off)
+                 *off = (tree_fits_uhwi_p (offset)
+                         ? tree_to_uhwi (offset) : HOST_WIDE_INT_MAX);
+
+               x = gimple_assign_rhs1 (def);
+               return get_origin_and_offset (x, fldoff, NULL);
+             }
+           else if (code == VAR_DECL)
+             {
+               x = gimple_assign_rhs1 (def);
+               return get_origin_and_offset (x, fldoff, off);
+             }
+         }
+       else if (gimple_nop_p (def) && SSA_NAME_VAR (x))
+         x = SSA_NAME_VAR (x);
+      }
+
+    default:
+      break;
+    }
+
+  return x;
+}
+
+/* If ARG refers to the same (sub)object or array element as described
+   by DST and DST_FLD, return the byte offset into the struct member or
+   array element referenced by ARG.  Otherwise return HOST_WIDE_INT_MIN
+   to indicate that ARG and DST do not refer to the same object.  */
+
+static HOST_WIDE_INT
+alias_offset (tree arg, tree dst, HOST_WIDE_INT dst_fld)
+{
+  /* See if the argument refers to the same base object as the destination
+     of the formatted function call, and if so, try to determine if they
+     can alias.  */
+  if (!arg || !dst || !ptr_derefs_may_alias_p (arg, dst))
+    return HOST_WIDE_INT_MIN;
+
+  /* The two arguments may refer to the same object.  If they both refer
+     to a struct member, see if the members are one and the same.  */
+  HOST_WIDE_INT arg_off = 0, arg_fld = 0;
+
+  tree arg_orig = get_origin_and_offset (arg, &arg_fld, &arg_off);
+
+  if (arg_orig == dst && arg_fld == dst_fld)
+    return arg_off;
+
+  return HOST_WIDE_INT_MIN;
+}
+
 /* Return the minimum and maximum number of characters formatted
    by the '%s' format directive and its wide character form for
    the argument ARG.  ARG can be null (for functions such as
    vsprinf).  */
 
 static fmtresult
-format_string (const directive &dir, tree arg)
+format_string (const directive &dir, tree arg, const vr_values *vr_values)
 {
   fmtresult res;
 
+  if (warn_restrict)
+    {
+      /* See if ARG might alias the destination of the call with
+        DST_ORIGIN and DST_FIELD.  If so, store the starting offset
+        so that the overlap can be determined for certain later,
+        when the amount of output of the call (including subsequent
+        directives) has been computed.  Otherwise, store HWI_MIN.  */
+      res.dst_offset = alias_offset (arg, dir.info->dst_origin,
+                                    dir.info->dst_field);
+    }
+
   /* 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, vr_values);
   if (slen.range.min == slen.range.max
       && slen.range.min < HOST_WIDE_INT_MAX)
     {
@@ -2202,13 +2473,14 @@ 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).  */
          res.range.max *= target_mb_len_max ();
          res.range.unlikely = res.range.max;
-         /* It's likely that the the total length is not more that
+         /* It's likely that the total length is not more that
             2 * wcslen (S).*/
          res.range.likely = res.range.min * 2;
 
@@ -2228,6 +2500,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
        {
@@ -2266,7 +2542,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;
@@ -2278,6 +2555,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;
@@ -2319,6 +2600,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 ())
        {
@@ -2328,6 +2611,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
        {
@@ -2337,10 +2621,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);
 }
@@ -2348,7 +2635,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, const vr_values *)
 {
   fmtresult res (dir.len);
   return res;
@@ -2358,7 +2645,7 @@ format_plain (const directive &dir, tree)
    should be diagnosed given the AVAILable space in the destination.  */
 
 static bool
-should_warn_p (const sprintf_dom_walker::call_info &info,
+should_warn_p (const call_info &info,
               const result_range &avail, const result_range &result)
 {
   if (result.max <= avail.min)
@@ -2429,7 +2716,7 @@ should_warn_p (const sprintf_dom_walker::call_info &info,
 
 static bool
 maybe_warn (substring_loc &dirloc, location_t argloc,
-           const sprintf_dom_walker::call_info &info,
+           const call_info &info,
            const result_range &avail_range, const result_range &res,
            const directive &dir)
 {
@@ -2466,121 +2753,114 @@ maybe_warn (substring_loc &dirloc, location_t argloc,
          /* 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')
-       {
-         /* 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, UNKNOWN_LOCATION, NULL, info.warnopt (),
-                         fmtstr, 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, argloc, NULL,
-                         info.warnopt (), fmtstr, dir.len,
-                         target_to_host (hostdir, sizeof hostdir, dir.beg),
-                         res.min, navail);
+       {
+         /* This is the terminating nul.  */
+         gcc_assert (res.min == 1 && res.min == res.max);
+
+         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 == 0 && res.max < maxbytes)
+      if (res.min == res.max)
        {
-         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, argloc, NULL,
-                         info.warnopt (), fmtstr, dir.len,
-                         target_to_host (hostdir, sizeof hostdir, dir.beg),
-                         res.max, 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)
+       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, argloc, NULL,
-                         info.warnopt (), fmtstr, dir.len,
-                         target_to_host (hostdir, sizeof hostdir, 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, argloc, NULL,
-                         info.warnopt (), fmtstr, dir.len,
-                         target_to_host (hostdir, sizeof hostdir, 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, argloc, NULL,
-                     info.warnopt (), fmtstr, dir.len,
+       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);
     }
@@ -2594,140 +2874,164 @@ maybe_warn (substring_loc &dirloc, location_t argloc,
       /* 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, UNKNOWN_LOCATION, 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, argloc, NULL,
-                     info.warnopt (), fmtstr, dir.len,
-                     target_to_host (hostdir, sizeof hostdir, 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, argloc, NULL,
-                     info.warnopt (), fmtstr, dir.len,
-                     target_to_host (hostdir, sizeof hostdir, 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, argloc, NULL,
-                     info.warnopt (), fmtstr, dir.len,
-                     target_to_host (hostdir, sizeof hostdir, 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, argloc, NULL,
-                     info.warnopt (), fmtstr, dir.len,
-                     target_to_host (hostdir, sizeof hostdir, 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, argloc, NULL,
-                 info.warnopt (), fmtstr, dir.len,
+    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);
 }
 
+/* Given the formatting result described by RES and NAVAIL, the number
+   of available in the destination, return the range of bytes remaining
+   in the destination.  */
+
+static inline result_range
+bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
+{
+  result_range range;
+
+  if (HOST_WIDE_INT_MAX <= navail)
+    {
+      range.min = range.max = range.likely = range.unlikely = navail;
+      return range;
+    }
+
+  /* The lower bound of the available range is the available size
+     minus the maximum output size, and the upper bound is the size
+     minus the minimum.  */
+  range.max = res.range.min < navail ? navail - res.range.min : 0;
+
+  range.likely = res.range.likely < navail ? navail - res.range.likely : 0;
+
+  if (res.range.max < HOST_WIDE_INT_MAX)
+    range.min = res.range.max < navail ? navail - res.range.max : 0;
+  else
+    range.min = range.likely;
+
+  range.unlikely = (res.range.unlikely < navail
+                   ? navail - res.range.unlikely : 0);
+
+  return range;
+}
+
 /* Compute the length of the output resulting from the directive DIR
    in a call described by INFO and update the overall result of the call
    in *RES.  Return true if the directive has been handled.  */
 
 static bool
-format_directive (const sprintf_dom_walker::call_info &info,
-                 format_result *res, const directive &dir)
+format_directive (const call_info &info,
+                 format_result *res, const directive &dir,
+                 const class vr_values *vr_values)
 {
   /* Offset of the beginning of the directive from the beginning
      of the format string.  */
@@ -2752,7 +3056,7 @@ format_directive (const sprintf_dom_walker::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
@@ -2802,8 +3106,9 @@ format_directive (const sprintf_dom_walker::call_info &info,
   if (fmtres.nullp)
     {
       fmtwarn (dirloc, argloc, NULL, info.warnopt (),
-              "%<%.*s%> directive argument is null",
-              dirlen, target_to_host (hostdir, sizeof hostdir, dir.beg));
+              "%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;
@@ -2817,6 +3122,12 @@ format_directive (const sprintf_dom_walker::call_info &info,
      NUL that's appended after the format string has been processed.  */
   result_range avail_range = bytes_remaining (info.objsize, *res);
 
+  /* If the argument aliases a part of the destination of the formatted
+     call at offset FMTRES.DST_OFFSET append the directive and its result
+     to the set of aliases for later processing.  */
+  if (fmtres.dst_offset != HOST_WIDE_INT_MIN)
+    res->append_alias (dir, fmtres.dst_offset, fmtres.range);
+
   bool warned = res->warned;
 
   if (!warned)
@@ -2846,15 +3157,20 @@ format_directive (const sprintf_dom_walker::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)))
     {
@@ -2863,30 +3179,31 @@ format_directive (const sprintf_dom_walker::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, argloc, NULL,
-                         info.warnopt (),
+       warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
                          "%<%.*s%> directive output of %wu bytes exceeds "
-                         "minimum required size of 4095",
-                         dirlen,
+                         "minimum required size of 4095", dirlen,
                          target_to_host (hostdir, sizeof hostdir, 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, argloc, NULL,
-                           info.warnopt (), fmtstr, dirlen,
-                           target_to_host (hostdir, sizeof hostdir, dir.beg),
-                           fmtres.range.min, fmtres.range.max);
-       }
+      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?  */
@@ -2902,44 +3219,75 @@ format_directive (const sprintf_dom_walker::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, 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
+      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, argloc, NULL,
-                           info.warnopt (), fmtstr, dirlen,
-                           target_to_host (hostdir, sizeof hostdir, 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)
     {
@@ -2956,67 +3304,90 @@ format_directive (const sprintf_dom_walker::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 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 (sprintf_dom_walker::call_info &info,
+parse_directive (call_info &info,
                 directive &dir, format_result *res,
-                const char *str, unsigned *argno)
+                const char *str, unsigned *argno,
+                const vr_values *vr_values)
 {
   const char *pcnt = strchr (str, target_percent);
   dir.beg = str;
@@ -3031,16 +3402,21 @@ parse_directive (sprintf_dom_walker::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;
     }
 
+  /* Set the directive argument's number to correspond to its position
+     in the formatted function call's argument list.  */
+  dir.argno = *argno;
+
   const char *pf = pcnt + 1;
 
     /* POSIX numbered argument index or zero when none.  */
@@ -3078,7 +3454,7 @@ parse_directive (sprintf_dom_walker::call_info &info,
         width and sort it out later after the next character has
         been seen.  */
       pwidth = pf;
-      width = target_strtol10 (&pf, &werange);
+      width = target_strtowi (&pf, &werange);
     }
   else if (target_to_host (*pf) == '*')
     {
@@ -3160,7 +3536,7 @@ parse_directive (sprintf_dom_walker::call_info &info,
        {
          werange = 0;
          pwidth = pf;
-         width = target_strtol10 (&pf, &werange);
+         width = target_strtowi (&pf, &werange);
        }
       else if (target_to_host (*pf) == '*')
        {
@@ -3193,7 +3569,7 @@ parse_directive (sprintf_dom_walker::call_info &info,
       if (ISDIGIT (target_to_host (*pf)))
        {
          pprec = pf;
-         precision = target_strtol10 (&pf, &perange);
+         precision = target_strtowi (&pf, &perange);
        }
       else if (target_to_host (*pf) == '*')
        {
@@ -3307,12 +3683,15 @@ parse_directive (sprintf_dom_walker::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;
 
@@ -3333,7 +3712,7 @@ parse_directive (sprintf_dom_walker::call_info &info,
   if (star_width)
     {
       if (INTEGRAL_TYPE_P (TREE_TYPE (star_width)))
-       dir.set_width (star_width);
+       dir.set_width (star_width, vr_values);
       else
        {
          /* Width specified by a va_list takes on the range [0, -INT_MIN]
@@ -3344,7 +3723,7 @@ parse_directive (sprintf_dom_walker::call_info &info,
     }
   else
     {
-      if (width == LONG_MAX && werange)
+      if (width == HOST_WIDE_INT_MAX && werange)
        {
          size_t begin = dir.beg - info.fmtstr + (pwidth - pcnt);
          size_t caret = begin + (werange - pcnt);
@@ -3355,9 +3734,9 @@ parse_directive (sprintf_dom_walker::call_info &info,
          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",
-                  dir.len, target_to_host (hostdir, sizeof hostdir, dir.beg));
+         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);
@@ -3366,7 +3745,7 @@ parse_directive (sprintf_dom_walker::call_info &info,
   if (star_precision)
     {
       if (INTEGRAL_TYPE_P (TREE_TYPE (star_precision)))
-       dir.set_precision (star_precision);
+       dir.set_precision (star_precision, vr_values);
       else
        {
          /* Precision specified by a va_list takes on the range [-1, INT_MAX]
@@ -3377,7 +3756,7 @@ parse_directive (sprintf_dom_walker::call_info &info,
     }
   else
     {
-      if (precision == LONG_MAX && perange)
+      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;
@@ -3389,9 +3768,9 @@ parse_directive (sprintf_dom_walker::call_info &info,
          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",
-                  dir.len, target_to_host (hostdir, sizeof hostdir, dir.beg));
+         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);
@@ -3407,25 +3786,34 @@ parse_directive (sprintf_dom_walker::call_info &info,
 
   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);
     }
@@ -3433,6 +3821,136 @@ parse_directive (sprintf_dom_walker::call_info &info,
   return dir.len;
 }
 
+/* Diagnose overlap between destination and %s directive arguments.  */
+
+static void
+maybe_warn_overlap (call_info &info, format_result *res)
+{
+  /* Two vectors of 1-based indices corresponding to either certainly
+     or possibly aliasing arguments.  */
+  auto_vec<int, 16> aliasarg[2];
+
+  /* Go through the array of potentially aliasing directives and collect
+     argument numbers of those that do or may overlap the destination
+     object given the full result.  */
+  for (unsigned i = 0; i != res->alias_count; ++i)
+    {
+      const format_result::alias_info &alias = res->aliases[i];
+
+      enum { possible = -1, none = 0, certain = 1 } overlap = none;
+
+      /* If the precision is zero there is no overlap.  (This only
+        considers %s directives and ignores %n.)  */
+      if (alias.dir.prec[0] == 0 && alias.dir.prec[1] == 0)
+       continue;
+
+      if (alias.offset == HOST_WIDE_INT_MAX
+         || info.dst_offset == HOST_WIDE_INT_MAX)
+       overlap = possible;
+      else if (alias.offset == info.dst_offset)
+       overlap = alias.dir.prec[0] == 0 ? possible : certain;
+      else
+       {
+         /* Determine overlap from the range of output and offsets
+            into the same destination as the source, and rule out
+            impossible overlap.  */
+         unsigned HOST_WIDE_INT albeg = alias.offset;
+         unsigned HOST_WIDE_INT dstbeg = info.dst_offset;
+
+         unsigned HOST_WIDE_INT alend = albeg + alias.range.min;
+         unsigned HOST_WIDE_INT dstend = dstbeg + res->range.min - 1;
+
+         if ((albeg <= dstbeg && alend > dstbeg)
+             || (albeg >= dstbeg && albeg < dstend))
+           overlap = certain;
+         else
+           {
+             alend = albeg + alias.range.max;
+             if (alend < albeg)
+               alend = HOST_WIDE_INT_M1U;
+
+             dstend = dstbeg + res->range.max - 1;
+             if (dstend < dstbeg)
+               dstend = HOST_WIDE_INT_M1U;
+
+             if ((albeg >= dstbeg && albeg <= dstend)
+                 || (alend >= dstbeg && alend <= dstend))
+               overlap = possible;
+           }
+       }
+
+      if (overlap == none)
+       continue;
+
+      /* Append the 1-based argument number.  */
+      aliasarg[overlap != certain].safe_push (alias.dir.argno + 1);
+
+      /* Disable any kind of optimization.  */
+      res->range.unlikely = HOST_WIDE_INT_M1U;
+    }
+
+  tree arg0 = gimple_call_arg (info.callstmt, 0);
+  location_t loc = gimple_location (info.callstmt);
+
+  bool aliaswarn = false;
+
+  unsigned ncertain = aliasarg[0].length ();
+  unsigned npossible = aliasarg[1].length ();
+  if (ncertain && npossible)
+    {
+      /* If there are multiple arguments that overlap, some certainly
+        and some possibly, handle both sets in a single diagnostic.  */
+      aliaswarn
+       = warning_at (loc, OPT_Wrestrict,
+                     "%qE arguments %Z and maybe %Z overlap destination "
+                     "object %qE",
+                     info.func, aliasarg[0].address (), ncertain,
+                     aliasarg[1].address (), npossible,
+                     info.dst_origin);
+    }
+  else if (ncertain)
+    {
+      /* There is only one set of two or more arguments and they all
+        certainly overlap the destination.  */
+      aliaswarn
+       = warning_n (loc, OPT_Wrestrict, ncertain,
+                    "%qE argument %Z overlaps destination object %qE",
+                    "%qE arguments %Z overlap destination object %qE",
+                    info.func, aliasarg[0].address (), ncertain,
+                    info.dst_origin);
+    }
+  else if (npossible)
+    {
+      /* There is only one set of two or more arguments and they all
+        may overlap (but need not).  */
+      aliaswarn
+       = warning_n (loc, OPT_Wrestrict, npossible,
+                    "%qE argument %Z may overlap destination object %qE",
+                    "%qE arguments %Z may overlap destination object %qE",
+                    info.func, aliasarg[1].address (), npossible,
+                    info.dst_origin);
+    }
+
+  if (aliaswarn)
+    {
+      res->warned = true;
+
+      if (info.dst_origin != arg0)
+       {
+         /* If its location is different from the first argument of the call
+            point either at the destination object itself or at the expression
+            that was used to determine the overlap.  */
+         loc = (DECL_P (info.dst_origin)
+                ? DECL_SOURCE_LOCATION (info.dst_origin)
+                : EXPR_LOCATION (info.dst_origin));
+         if (loc != UNKNOWN_LOCATION)
+           inform (loc,
+                   "destination object referenced by %<restrict%>-qualified "
+                   "argument 1 was declared here");
+       }
+    }
+}
+
 /* Compute the length of the output resulting from the call to a formatted
    output function described by INFO and store the result of the call in
    *RES.  Issue warnings for detected past the end writes.  Return true
@@ -3440,9 +3958,8 @@ parse_directive (sprintf_dom_walker::call_info &info,
    on, false otherwise (e.g., when a unknown or unhandled directive was seen
    that caused the processing to be terminated early).  */
 
-bool
-sprintf_dom_walker::compute_format_length (call_info &info,
-                                           format_result *res)
+static bool
+compute_format_length (call_info &info, format_result *res, const vr_values *vr)
 {
   if (dump_file)
     {
@@ -3451,18 +3968,19 @@ sprintf_dom_walker::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;
 
@@ -3472,37 +3990,47 @@ sprintf_dom_walker::compute_format_length (call_info &info,
   /* The variadic argument counter.  */
   unsigned argno = info.argidx;
 
+  bool success = true;
+
   for (const char *pf = info.fmtstr; ; ++dirno)
     {
-      directive dir = directive ();
-      dir.dirno = dirno;
+      directive dir (&info, dirno);
 
-      size_t n = parse_directive (info, dir, res, pf, &argno);
+      size_t n = parse_directive (info, dir, res, pf, &argno, vr);
 
       /* Return failure if the format function fails.  */
-      if (!format_directive (info, res, dir))
+      if (!format_directive (info, res, dir, vr))
        return false;
 
-      /* Return success the directive is zero bytes long and it's
-        the last think in the format string (i.e., it's the terminating
+      /* Return success when the directive is zero bytes long and it's
+        the last thing in the format string (i.e., it's the terminating
         nul, which isn't really a directive but handling it as one makes
         things simpler).  */
       if (!n)
-       return *pf == '\0';
+       {
+         success = *pf == '\0';
+         break;
+       }
 
       pf += n;
     }
 
+  maybe_warn_overlap (info, res);
+
   /* The complete format string was processed (with or without warnings).  */
-  return true;
+  return success;
 }
 
 /* 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 ();
 
@@ -3516,7 +4044,7 @@ 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;
 }
 
 /* Return true if the call described by INFO with result RES safe to
@@ -3524,11 +4052,11 @@ get_destination_size (tree dest)
    of its return values.  */
 
 static bool
-is_call_safe (const sprintf_dom_walker::call_info &info,
+is_call_safe (const call_info &info,
              const format_result &res, bool under4k,
              unsigned HOST_WIDE_INT retval[2])
 {
-  if (under4k && !res.under4k)
+  if (under4k && !res.posunder4k)
     return false;
 
   /* The minimum return value.  */
@@ -3583,7 +4111,7 @@ is_call_safe (const sprintf_dom_walker::call_info &info,
 
 static bool
 try_substitute_return_value (gimple_stmt_iterator *gsi,
-                            const sprintf_dom_walker::call_info &info,
+                            const call_info &info,
                             const format_result &res)
 {
   tree lhs = gimple_get_lhs (info.callstmt);
@@ -3592,7 +4120,7 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
   bool removed = false;
 
   /* The minimum and maximum return value.  */
-  unsigned HOST_WIDE_INT retval[2];
+  unsigned HOST_WIDE_INT retval[2] = {0};
   bool safe = is_call_safe (info, res, true, retval);
 
   if (safe
@@ -3602,10 +4130,10 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
         are badly declared.  */
       && !stmt_ends_bb_p (info.callstmt))
     {
-      tree cst = build_int_cst (integer_type_node, retval[0]);
+      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.  */
@@ -3646,7 +4174,7 @@ 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;
 
@@ -3678,13 +4206,14 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
          const char *what = setrange ? "Setting" : "Discarding";
          if (retval[0] != retval[1])
            fprintf (dump_file,
-                    "  %s %s-bounds return value range [%llu, %llu].\n",
-                    what, inbounds,
-                    (unsigned long long)retval[0],
-                    (unsigned long long)retval[1]);
+                    "  %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)retval[0]);
+           fprintf (dump_file, "  %s %s-bounds return value "
+                    HOST_WIDE_INT_PRINT_UNSIGNED ".\n",
+                    what, inbounds, retval[0]);
        }
     }
 
@@ -3700,7 +4229,7 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
 
 static bool
 try_simplify_call (gimple_stmt_iterator *gsi,
-                  const sprintf_dom_walker::call_info &info,
+                  const call_info &info,
                   const format_result &res)
 {
   unsigned HOST_WIDE_INT dummy[2];
@@ -3722,21 +4251,72 @@ try_simplify_call (gimple_stmt_iterator *gsi,
   return false;
 }
 
-/* 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.  */
+/* 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;
+}
+
+}   /* Unnamed namespace.  */
+
+/* Determine if a GIMPLE call at *GSI 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
-sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
+handle_printf_call (gimple_stmt_iterator *gsi, const vr_values *vr_values)
 {
+  init_target_to_host_charmap ();
+
   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;
@@ -3744,17 +4324,70 @@ sprintf_dom_walker::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, ...)
@@ -3789,6 +4422,38 @@ sprintf_dom_walker::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)
@@ -3830,16 +4495,19 @@ sprintf_dom_walker::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
@@ -3864,32 +4532,58 @@ sprintf_dom_walker::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_equiv *vr
+           = CONST_CAST (class vr_values *, vr_values)->get_value_range (size);
+
+         if (range_int_cst_p (vr))
+           {
+             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 ())
            {
-             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 ());
+             /* 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
@@ -3899,7 +4593,7 @@ sprintf_dom_walker::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);
@@ -3919,14 +4613,15 @@ sprintf_dom_walker::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;
        }
 
@@ -3939,7 +4634,7 @@ sprintf_dom_walker::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 (),
@@ -3948,14 +4643,15 @@ sprintf_dom_walker::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;
     }
 
@@ -3963,11 +4659,30 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
   if (!info.fmtstr)
     return false;
 
+  if (warn_restrict)
+    {
+      /* Compute the origin of the destination pointer and its offset
+        from the base object/pointer if possible.  */
+      info.dst_offset = 0;
+      info.dst_origin = get_origin_and_offset (dstptr, &info.dst_field,
+                                              &info.dst_offset);
+    }
+
   /* The result is the number of bytes output by the formatted function,
      including the terminating NUL.  */
-  format_result res = format_result ();
+  format_result res;
+
+  /* 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);
+  bool success = compute_format_length (info, &res, vr_values);
+  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
@@ -3991,49 +4706,3 @@ sprintf_dom_walker::handle_gimple_call (gimple_stmt_iterator *gsi)
 
   return call_removed;
 }
-
-edge
-sprintf_dom_walker::before_dom_children (basic_block 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);
-
-      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;
-}
-
-/* Execute the pass for function FUN.  */
-
-unsigned int
-pass_sprintf_length::execute (function *fun)
-{
-  init_target_to_host_charmap ();
-
-  calculate_dominance_info (CDI_DOMINATORS);
-
-  sprintf_dom_walker sprintf_dom_walker;
-  sprintf_dom_walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun));
-
-  /* Clean up object size info.  */
-  fini_object_sizes ();
-  return 0;
-}
-
-}   /* Unnamed namespace.  */
-
-/* Return a pointer to a pass object newly constructed from the context
-   CTXT.  */
-
-gimple_opt_pass *
-make_pass_sprintf_length (gcc::context *ctxt)
-{
-  return new pass_sprintf_length (ctxt);
-}