diff mbox

move snprintf truncation warnings under own option ()

Message ID 81014ef4-2803-82b7-d24e-d32cfb69cad6@gmail.com
State New
Headers show

Commit Message

Martin Sebor Jan. 4, 2017, 12:04 a.m. UTC
The -Wformat-length option warns about both overflow and truncation.
I had initially debated introducing two options, one for each of
the two kinds of problems, but decided to go with just one and
consider breaking it up based on feedback.

I feel that there has now been sufficient feedback (e.g., bugs 77708
and 78913) to justify breaking up the checkers and providing a new
option to control truncation independently.

The attached patch adds a new option, -Wformat-truncation=level that
accomplishes this.  At level 1 the new option only warns on certain
truncation, or on likely truncation in snprintf calls whose return
value is unused (using the return value suppresses the warning in
these cases).

This change eliminates the -Wformat-length warnings from a build
of the Linux kernel, replacing their 43 instances with 32 of the
-Wformat-truncation warning.  With one exception, they're all for
snprintf calls whose return values is unused (and thus possible
sources of bugs).

If/when this patch is approved I'd like to rename -Wformat-length
to -Wformat-overflow to make the option's refined purpose clear,
and for consistency with the -Wstringop-overflow option.

Martin

Comments

Jeff Law Jan. 6, 2017, 8:14 p.m. UTC | #1
On 01/03/2017 05:04 PM, Martin Sebor wrote:
> The -Wformat-length option warns about both overflow and truncation.

> I had initially debated introducing two options, one for each of

> the two kinds of problems, but decided to go with just one and

> consider breaking it up based on feedback.

>

> I feel that there has now been sufficient feedback (e.g., bugs 77708

> and 78913) to justify breaking up the checkers and providing a new

> option to control truncation independently.

>

> The attached patch adds a new option, -Wformat-truncation=level that

> accomplishes this.  At level 1 the new option only warns on certain

> truncation, or on likely truncation in snprintf calls whose return

> value is unused (using the return value suppresses the warning in

> these cases).

>

> This change eliminates the -Wformat-length warnings from a build

> of the Linux kernel, replacing their 43 instances with 32 of the

> -Wformat-truncation warning.  With one exception, they're all for

> snprintf calls whose return values is unused (and thus possible

> sources of bugs).

>

> If/when this patch is approved I'd like to rename -Wformat-length

> to -Wformat-overflow to make the option's refined purpose clear,

> and for consistency with the -Wstringop-overflow option.

>

> Martin

>

> gcc-78913.diff

>

>

> PR tree-optimization/78913 - Probably misleading error reported by -Wformat-length

> PR middle-end/77708 - -Wformat-length %s warns for snprintf

>

> gcc/c-family/ChangeLog:

>

> 	PR tree-optimization/78913

> 	PR middle-end/77708

> 	* c.opt (-Wformat-truncation): New option.

>

> gcc/testsuite/ChangeLog:

>

> 	PR tree-optimization/78913

> 	PR middle-end/77708

> 	* gcc.dg/tree-ssa/builtin-snprintf-warn-1.c: New test.

> 	* gcc.dg/tree-ssa/builtin-snprintf-warn-2.c: New test.

> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL test cases failing

> 	due to bug 78969.

>

> gcc/ChangeLog:

>

> 	PR tree-optimization/78913

> 	PR middle-end/77708

> 	* doc/invoke.texi (Warning Options): Document -Wformat-truncation.

> 	* gimple-ssa-sprintf.c (call_info::reval_used, call_info::warnopt):

> 	New member functions.

> 	(format_directive): Used them.

> 	(add_bytes): Same.

> 	(pass_sprintf_length::handle_gimple_call): Same.

OK.

jeff
Martin Sebor Jan. 8, 2017, 11:49 p.m. UTC | #2
>> PR tree-optimization/78913 - Probably misleading error reported by

>> -Wformat-length

>> PR middle-end/77708 - -Wformat-length %s warns for snprintf

>>

>> gcc/c-family/ChangeLog:

>>

>>     PR tree-optimization/78913

>>     PR middle-end/77708

>>     * c.opt (-Wformat-truncation): New option.

>>

>> gcc/testsuite/ChangeLog:

>>

>>     PR tree-optimization/78913

>>     PR middle-end/77708

>>     * gcc.dg/tree-ssa/builtin-snprintf-warn-1.c: New test.

>>     * gcc.dg/tree-ssa/builtin-snprintf-warn-2.c: New test.

>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL test cases failing

>>     due to bug 78969.

>>

>> gcc/ChangeLog:

>>

>>     PR tree-optimization/78913

>>     PR middle-end/77708

>>     * doc/invoke.texi (Warning Options): Document -Wformat-truncation.

>>     * gimple-ssa-sprintf.c (call_info::reval_used, call_info::warnopt):

>>     New member functions.

>>     (format_directive): Used them.

>>     (add_bytes): Sam

>>     (pass_sprintf_length::handle_gimple_call): Same.

> OK.

>

> jeff


Thanks.  The option exposed a few instances of possible unchecked
truncation in GCC that I had missed, most likely because I'd looked
at the test results for one of my other patches by mistake.  Since
the fixes were all trivial and involved increasing local buffers by
a few bytes I committed the result in r244210 without posting the
updated patch for review first.

Martin
diff mbox

Patch

PR tree-optimization/78913 - Probably misleading error reported by -Wformat-length
PR middle-end/77708 - -Wformat-length %s warns for snprintf

gcc/c-family/ChangeLog:

	PR tree-optimization/78913
	PR middle-end/77708
	* c.opt (-Wformat-truncation): New option.

gcc/testsuite/ChangeLog:

	PR tree-optimization/78913
	PR middle-end/77708
	* gcc.dg/tree-ssa/builtin-snprintf-warn-1.c: New test.
	* gcc.dg/tree-ssa/builtin-snprintf-warn-2.c: New test.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL test cases failing
	due to bug 78969.

gcc/ChangeLog:

	PR tree-optimization/78913
	PR middle-end/77708
	* doc/invoke.texi (Warning Options): Document -Wformat-truncation.
	* gimple-ssa-sprintf.c (call_info::reval_used, call_info::warnopt):
	New member functions.
	(format_directive): Used them.
	(add_bytes): Same.
	(pass_sprintf_length::handle_gimple_call): Same.

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 3c06aec..849634c 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -537,6 +537,11 @@  Wformat-signedness
 C ObjC C++ ObjC++ Var(warn_format_signedness) Warning
 Warn about sign differences with format functions.
 
+Wformat-truncation
+C ObjC C++ ObjC++ Warning Alias(Wformat-truncation=, 1, 0)
+Warn about calls to snprintf and similar functions that truncate output.
+Same as -Wformat-truncation=1.
+
 Wformat-y2k
 C ObjC C++ ObjC++ Var(warn_format_y2k) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=,warn_format >= 2, 0)
 Warn about strftime formats yielding 2-digit years.
@@ -554,6 +559,10 @@  C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format_length) Warning
 Warn about function calls with format strings that write past the end
 of the destination region.
 
+Wformat-truncation=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format_trunc) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
+Warn about calls to snprintf and similar functions that truncate output.
+
 Wignored-qualifiers
 C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra)
 Warn whenever type qualifiers are ignored.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index a8f8efe..2ae265a 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -276,7 +276,8 @@  Objective-C and Objective-C++ Dialects}.
 -Werror  -Werror=* -Wfatal-errors -Wfloat-equal  -Wformat  -Wformat=2 @gol
 -Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=@var{n} @gol
 -Wformat-nonliteral @gol
--Wformat-security  -Wformat-signedness  -Wformat-y2k -Wframe-address @gol
+-Wformat-security  -Wformat-signedness -Wformat-truncation=@var{n} @gol
+-Wformat-y2k -Wframe-address @gol
 -Wframe-larger-than=@var{len} -Wno-free-nonheap-object -Wjump-misses-init @gol
 -Wignored-qualifiers  -Wignored-attributes  -Wincompatible-pointer-types @gol
 -Wimplicit  -Wimplicit-fallthrough  -Wimplicit-fallthrough=@var{n} @gol
@@ -3959,10 +3960,9 @@  Unix Specification says that such unused arguments are allowed.
 @opindex Wformat-length
 @opindex Wno-format-length
 Warn about calls to formatted input/output functions such as @code{sprintf}
-that might overflow the destination buffer, or about bounded functions such
-as @code{snprintf} that might result in output truncation.  When the exact
-number of bytes written by a format directive cannot be determined at
-compile-time it is estimated based on heuristics that depend on the
+and @code{vsprintf} that might overflow the destination buffer.  When the
+exact number of bytes written by a format directive cannot be determined
+at compile-time it is estimated based on heuristics that depend on the
 @var{level} argument and on optimization.  While enabling optimization
 will in most cases improve the accuracy of the warning, it may also
 result in false positives.
@@ -3974,15 +3974,14 @@  result in false positives.
 @opindex Wno-format-length
 Level @var{1} of @option{-Wformat-length} enabled by @option{-Wformat}
 employs a conservative approach that warns only about calls that most
-likely overflow the buffer or result in output truncation.  At this
-level, numeric arguments to format directives with unknown values are
-assumed to have the value of one, and strings of unknown length to be
-empty.  Numeric arguments that are known to be bounded to a subrange
-of their type, or string arguments whose output is bounded either by
-their directive's precision or by a finite set of string literals, are
-assumed to take on the value within the range that results in the most
-bytes on output.  For example, the call to @code{sprintf} below is
-diagnosed because even with both @var{a} and @var{b} equal to zero,
+likely overflow the buffer.  At this level, numeric arguments to format
+directives with unknown values are assumed to have the value of one, and
+strings of unknown length to be empty.  Numeric arguments that are known
+to be bounded to a subrange of their type, or string arguments whose output
+is bounded either by their directive's precision or by a finite set of
+string literals, are assumed to take on the value within the range that
+results in the most bytes on output.  For example, the call to @code{sprintf}
+below is diagnosed because even with both @var{a} and @var{b} equal to zero,
 the terminating NUL character (@code{'\0'}) appended by the function
 to the destination buffer will be written past its end.  Increasing
 the size of the buffer by a single byte is sufficient to avoid the
@@ -3998,14 +3997,13 @@  void f (int a, int b)
 
 @item -Wformat-length=2
 Level @var{2} warns also about calls that might overflow the destination
-buffer or result in truncation given an argument of sufficient length
-or magnitude.  At level @var{2}, unknown numeric arguments are assumed
-to have the minimum representable value for signed types with a precision
-greater than 1, and the maximum representable value otherwise.  Unknown
-string arguments whose length cannot be assumed to be bounded either by
-the directive's precision, or by a finite set of string literals they
-may evaluate to, or the character array they may point to, are assumed
-to be 1 character long.
+buffer given an argument of sufficient length or magnitude.  At level
+@var{2}, unknown numeric arguments are assumed to have the minimum
+representable value for signed types with a precision greater than 1, and
+the maximum representable value otherwise.  Unknown string arguments whose
+length cannot be assumed to be bounded either by the directive's precision,
+or by a finite set of string literals they may evaluate to, or the character
+array they may point to, are assumed to be 1 character long.
 
 At level @var{2}, the call in the example above is again diagnosed, but
 this time because with @var{a} equal to a 32-bit @code{INT_MIN} the first
@@ -4075,6 +4073,35 @@  included in @option{-Wformat-nonliteral}.)
 If @option{-Wformat} is specified, also warn if the format string
 requires an unsigned argument and the argument is signed and vice versa.
 
+@item -Wformat-truncation
+@itemx -Wformat-truncation=@var{level}
+@opindex Wformat-truncation
+@opindex Wno-format-truncation
+Warn about calls to formatted input/output functions such as @code{snprintf}
+and @code{vsnprintf} that might result in output truncation.  When the exact
+number of bytes written by a format directive cannot be determined at
+compile-time it is estimated based on heuristics that depend on
+the @var{level} argument and on optimization.  While enabling optimization
+will in most cases improve the accuracy of the warning, it may also result
+in false positives.  Except as noted otherwise, the option uses the same
+logic @option{-Wformat-length}.
+
+@table @gcctabopt
+@item -Wformat-truncation
+@item -Wformat-truncation=1
+@opindex Wformat-truncation
+@opindex Wno-format-length
+Level @var{1} of @option{-Wformat-truncation} enabled by @option{-Wformat}
+employs a conservative approach that warns only about calls to bounded
+functions whose return value is unused and that will most likely result
+in output truncatation.
+
+@item -Wformat-truncation=2
+Level @var{2} warns also about calls to bounded functions whose return
+value is used and that might result in truncation given an argument of
+sufficient length or magnitude.
+@end table
+
 @item -Wformat-y2k
 @opindex Wformat-y2k
 @opindex Wno-format-y2k
@@ -8413,8 +8440,8 @@  if (snprintf (buf, "%08x", i) >= sizeof buf)
 
 The @option{-fprintf-return-value} option relies on other optimizations
 and yields best results with @option{-O2}.  It works in tandem with the
-@option{-Wformat-length} option.  The @option{-fprintf-return-value}
-option is enabled by default.
+@option{-Wformat-length} and @option{-Wformat-truncation} options.
+The @option{-fprintf-return-value} option is enabled by default.
 
 @item -fno-peephole
 @itemx -fno-peephole2
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 907a064..7b3e5a1 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -712,6 +712,18 @@  struct pass_sprintf_length::call_info
      buffer as a request to compute the size of output without actually
      writing any.  */
   bool nowrite;
+
+  /* Return true if the called function's return value is used.  */
+  bool retval_used () const
+  {
+    return gimple_get_lhs (callstmt);
+  }
+
+  /* Return the warning option corresponding to the called function.  */
+  int warnopt () const
+  {
+    return bounded ? OPT_Wformat_truncation_ : OPT_Wformat_length_;
+  }
 };
 
 /* Return the result of formatting the '%%' directive.  */
@@ -1925,8 +1937,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 
   if (fmtres.nullp)
     {
-      fmtwarn (dirloc, pargrange, NULL,
-	       OPT_Wformat_length_,
+      fmtwarn (dirloc, pargrange, NULL, info.warnopt (),
 	       "%<%.*s%> directive argument is null",
 	       (int)cvtlen, cvtbeg);
 
@@ -1961,8 +1972,8 @@  format_directive (const pass_sprintf_length::call_info &info,
 			    "%wu bytes into a region of size %wu")
 		       : G_("%<%.*s%> directive writing %wu bytes "
 			    "into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
+		  warned = fmtwarn (dirloc, pargrange, NULL, info.warnopt (),
+				    fmtstr,
 				    (int)cvtlen, cvtbeg, fmtres.range.min,
 				    navail);
 		}
@@ -1976,7 +1987,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 		       : G_("%<%.*s%> directive writing between %wu and "
 			    "%wu bytes into a region of size %wu"));
 		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
+				    info.warnopt (), fmtstr,
 				    (int)cvtlen, cvtbeg,
 				    fmtres.range.min, fmtres.range.max, navail);
 		}
@@ -1989,16 +2000,20 @@  format_directive (const pass_sprintf_length::call_info &info,
 		       : G_("%<%.*s%> directive writing %wu or more bytes "
 			    "into a region of size %wu"));
 		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
+				    info.warnopt (), fmtstr,
 				    (int)cvtlen, cvtbeg,
 				    fmtres.range.min, navail);
 		}
 	    }
 	  else if (navail < fmtres.range.max
-		   && (((spec.specifier == 's'
-			 && fmtres.range.max < HOST_WIDE_INT_MAX)
-			/* && (spec.precision || spec.star_precision) */)
-		       || 1 < warn_format_length))
+		   && (spec.specifier != 's'
+		       || fmtres.range.max < HOST_WIDE_INT_MAX)
+		   && ((info.bounded
+			&& (!info.retval_used ()
+			    || warn_format_trunc > 1))
+		       || (!info.bounded
+			   && (spec.specifier == 's'
+			       || 1 < warn_format_length))))
 	    {
 	      /* The maximum directive output is longer than there is
 		 room in the destination and the output length is either
@@ -2013,7 +2028,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 		       : G_("%<%.*s%> directive writing %wu or more bytes "
 			    "into a region of size %wu"));
 		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
+				    info.warnopt (), fmtstr,
 				    (int)cvtlen, cvtbeg,
 				    fmtres.range.min, navail);
 		}
@@ -2027,7 +2042,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 		       : G_("%<%.*s%> directive writing between %wu and %wu "
 			    "bytes into a region of size %wu"));
 		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
+				    info.warnopt (), fmtstr,
 				    (int)cvtlen, cvtbeg,
 				    fmtres.range.min, fmtres.range.max,
 				    navail);
@@ -2061,7 +2076,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 		       "into a region of size %wu")));
 
 	  warned = fmtwarn (dirloc, pargrange, NULL,
-			    OPT_Wformat_length_, fmtstr,
+			    info.warnopt (), fmtstr,
 			    (int)cvtlen, cvtbeg, fmtres.range.min,
 			    navail);
 	}
@@ -2086,7 +2101,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 
       if (fmtres.range.min == fmtres.range.max)
 	warned = fmtwarn (dirloc, pargrange, NULL,
-			  OPT_Wformat_length_,
+			  info.warnopt (),
 			  "%<%.*s%> directive output of %wu bytes exceeds "
 			  "minimum required size of 4095",
 			  (int)cvtlen, cvtbeg, fmtres.range.min);
@@ -2100,7 +2115,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 		    "bytes exceeds minimum required size of 4095"));
 
 	  warned = fmtwarn (dirloc, pargrange, NULL,
-			    OPT_Wformat_length_, fmtstr,
+			    info.warnopt (), fmtstr,
 			    (int)cvtlen, cvtbeg,
 			    fmtres.range.min, fmtres.range.max);
 	}
@@ -2118,8 +2133,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 	 to exceed INT_MAX bytes.  */
 
       if (fmtres.range.min == fmtres.range.max)
-	warned = fmtwarn (dirloc, pargrange, NULL,
-			  OPT_Wformat_length_,
+	warned = fmtwarn (dirloc, pargrange, NULL, info.warnopt (),
 			  "%<%.*s%> directive output of %wu bytes causes "
 			  "result to exceed %<INT_MAX%>",
 			  (int)cvtlen, cvtbeg, fmtres.range.min);
@@ -2132,7 +2146,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 	       : G_ ("%<%.*s%> directive output between %wu and %wu "
 		     "bytes may cause result to exceed %<INT_MAX%>"));
 	  warned = fmtwarn (dirloc, pargrange, NULL,
-			    OPT_Wformat_length_, fmtstr,
+			    info.warnopt (), fmtstr,
 			    (int)cvtlen, cvtbeg,
 			    fmtres.range.min, fmtres.range.max);
 	}
@@ -2240,7 +2254,11 @@  add_bytes (const pass_sprintf_length::call_info &info,
 		  : G_("writing a terminating nul past the end "
 		       "of the destination")));
 
-	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_, text);
+	  if (!info.bounded
+	      || !boundrange
+	      || !info.retval_used ()
+	      || warn_format_trunc > 1)
+	    res->warned = fmtwarn (loc, NULL, NULL, info.warnopt (), text);
 	}
       else
 	{
@@ -2258,8 +2276,12 @@  add_bytes (const pass_sprintf_length::call_info &info,
 		  : G_("writing format character %#qc at offset %wu past "
 		       "the end of the destination")));
 
-	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
-				 text, info.fmtstr[off], off);
+	  if (!info.bounded
+	      || !boundrange
+	      || !info.retval_used ()
+	      || warn_format_trunc > 1)
+	    res->warned = fmtwarn (loc, NULL, NULL, info.warnopt (),
+				   text, info.fmtstr[off], off);
 	}
     }
 
@@ -2326,8 +2348,7 @@  add_bytes (const pass_sprintf_length::call_info &info,
 	 off + len - !!len);
 
       if (res->number_chars_min == res->number_chars_max)
-	res->warned = fmtwarn (loc, NULL, NULL,
-			       OPT_Wformat_length_,
+	res->warned = fmtwarn (loc, NULL, NULL, info.warnopt (),
 			       "output of %wu bytes causes "
 			       "result to exceed %<INT_MAX%>",
 			       res->number_chars_min - !end);
@@ -2339,8 +2360,7 @@  add_bytes (const pass_sprintf_length::call_info &info,
 		     "result to exceed %<INT_MAX%>")
 	       : G_ ("output between %wu and %wu bytes may cause "
 		     "result to exceed %<INT_MAX%>"));
-	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
-				 text,
+	  res->warned = fmtwarn (loc, NULL, NULL, info.warnopt (), text,
 				 res->number_chars_min - !end,
 				 res->number_chars_max - !end);
 	}
@@ -2930,14 +2950,13 @@  pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
 		 checking built-ins.  */
 	      if ((idx_objsize == HOST_WIDE_INT_M1U
 		   || !warn_stringop_overflow))
-		warning_at (gimple_location (info.callstmt),
-			    OPT_Wformat_length_,
+		warning_at (gimple_location (info.callstmt), info.warnopt (),
 			    "specified bound %wu exceeds maximum object size "
 			    "%wu",
 			    dstsize, target_size_max () / 2);
 	    }
 	  else if (dstsize > target_int_max ())
-	    warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
+	    warning_at (gimple_location (info.callstmt), info.warnopt (),
 			"specified bound %wu exceeds %<INT_MAX %>",
 			dstsize);
 	}
@@ -2988,7 +3007,7 @@  pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
 	     is not constant.  */
 	  location_t loc = gimple_location (info.callstmt);
 	  warning_at (EXPR_LOC_OR_LOC (dstptr, loc),
-		      OPT_Wformat_length_, "null destination pointer");
+		      info.warnopt (), "null destination pointer");
 	  return;
 	}
 
@@ -3004,7 +3023,7 @@  pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
 	  && (idx_objsize == HOST_WIDE_INT_M1U
 	      || !warn_stringop_overflow))
 	{
-	  warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
+	  warning_at (gimple_location (info.callstmt), info.warnopt (),
 		      "specified bound %wu exceeds the size %wu "
 		      "of the destination object", dstsize, objsize);
 	}
@@ -3017,7 +3036,7 @@  pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
 	 is not constant.  */
       location_t loc = gimple_location (info.callstmt);
       warning_at (EXPR_LOC_OR_LOC (info.format, loc),
-		  OPT_Wformat_length_, "null format string");
+		  info.warnopt (), "null format string");
       return;
     }
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-1.c
new file mode 100644
index 0000000..cc226ca
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-1.c
@@ -0,0 +1,73 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wformat -Wformat-truncation=1 -ftrack-macro-expansion=0" } */
+
+typedef struct
+{
+  char a0[0];
+  char a1[1];
+  char a2[2];
+  char a3[3];
+  char a4[4];
+  char ax[];
+} Arrays;
+
+char buffer[1024];
+#define buffer(size) (buffer + sizeof buffer - size)
+
+int value_range (int min, int max)
+{
+  extern int value (void);
+  int val = value ();
+  return val < min || max < val ? min : val;
+}
+
+#define R(min, max)  value_range (min, max)
+
+/* Verify that calls to snprintf whose return value is unused are
+   diagnosed if certain or possible truncation is detected.  */
+
+#define T(size, ...) \
+  __builtin_snprintf (buffer (size), size, __VA_ARGS__)
+
+void test_int_retval_unused (void)
+{
+  T (2, "%i", 123);          /* { dg-warning "output truncated" } */
+  T (2, "%i", R (1, 99));    /* { dg-warning "output may be truncated" } */
+  T (2, "%i", R (10, 99));   /* { dg-warning "output truncated" } */
+  T (3, "%i%i", R (1, 99), R (1, 99));   /* { dg-warning "output may be truncated" } */
+}
+
+void test_string_retval_unused (const Arrays *ar)
+{
+  T (1, "%-s", ar->a0);
+  T (1, "%-s", ar->a1);
+  T (1, "%-s", ar->a2);   /* { dg-warning "output may be truncated" } */
+}
+
+
+/* Verify that calls to snprintf whose return value is used are
+   diagnosed only if certain truncation is detected but not when
+   truncation is only possible but not certain.  */
+
+volatile int retval;
+
+#undef T
+#define T(size, ...) \
+  retval = __builtin_snprintf (buffer (size), size, __VA_ARGS__)
+
+void test_int_retval_used (void)
+{
+  T (2, "%i", 123);          /* { dg-warning "output truncated" } */
+  T (2, "%i", R (1, 99));
+  T (2, "%i", R (10, 99));   /* { dg-warning "output truncated" } */
+  T (3, "%i%i", R (1, 99), R (1, 99));
+}
+
+void test_string_retval_used (const Arrays *ar)
+{
+  T (1, "%-s", ar->a0);
+  T (1, "%-s", ar->a1);
+  T (1, "%-s", ar->a2);
+  T (1, "%-s", ar->a4);
+  T (1, "%-s", "123");   /* { dg-warning "output truncated" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-2.c
new file mode 100644
index 0000000..93c9f1b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-2.c
@@ -0,0 +1,70 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wformat -Wformat-truncation=2 -ftrack-macro-expansion=0" } */
+
+typedef struct
+{
+  char a0[0];
+  char a1[1];
+  char a2[2];
+  char a3[3];
+  char a4[4];
+  char ax[];
+} Arrays;
+
+char buffer[1024];
+#define buffer(size) (buffer + sizeof buffer - size)
+
+int value_range (int min, int max)
+{
+  extern int value (void);
+  int val = value ();
+  return val < min || max < val ? min : val;
+}
+
+#define R(min, max)  value_range (min, max)
+
+/* Verify that calls to snprintf whose return value is unused are
+   diagnosed if certain or possible truncation is detected.  */
+
+#define T(size, ...) \
+  __builtin_snprintf (buffer (size), size, __VA_ARGS__)
+
+void test_int_retval_unused (void)
+{
+  T (2, "%i", 123);          /* { dg-warning "output truncated" } */
+  T (2, "%i", R (1, 99));    /* { dg-warning "output may be truncated" } */
+  T (2, "%i", R (10, 99));   /* { dg-warning "output truncated" } */
+  T (3, "%i%i", R (1, 99), R (1, 99));   /* { dg-warning "output may be truncated" } */
+}
+
+void test_string_retval_unused (const Arrays *ar)
+{
+  T (1, "%-s", ar->a0);
+  T (1, "%-s", ar->a1);
+  T (1, "%-s", ar->a2);   /* { dg-warning "output may be truncated" } */
+}
+
+
+/* Verify that (at -Wformat-trunc=2) calls to snprintf whose return value
+   is used are diagnosed the same way as those whose value is unused.  */
+
+volatile int retval;
+
+#undef T
+#define T(size, ...) \
+  retval = __builtin_snprintf (buffer (size), size, __VA_ARGS__)
+
+void test_int_retval_used (void)
+{
+  T (2, "%i", 123);          /* { dg-warning "output truncated" } */
+  T (2, "%i", R (1, 99));    /* { dg-warning "output may be truncated" } */
+  T (2, "%i", R (10, 99));   /* { dg-warning "output truncated" } */
+  T (3, "%i%i", R (1, 99), R (1, 99));   /* { dg-warning "output may be truncated" } */
+}
+
+void test_string_retval_used (const Arrays *ar)
+{
+  T (1, "%-s", ar->a0);
+  T (1, "%-s", ar->a1);
+  T (1, "%-s", ar->a2);   /* { dg-warning "output may be truncated" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c
index 121ed4e..93c53a4 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c
@@ -51,7 +51,8 @@  void fuint (unsigned j, char *p)
 {
   if (j > 999)
     return;
-  snprintf (p, 4, "%3u", j);
+
+  snprintf (p, 4, "%3u", j);   /* { dg-bogus "may be truncated" "unsigned int" { xfail *-*-* } } */
 }
 
 void fint (int j, char *p)
@@ -61,8 +62,7 @@  void fint (int j, char *p)
   if (k > 999)
     return;
 
-  /* Range info isn't available here.  */
-  snprintf (p, 4, "%3u", k);
+  snprintf (p, 4, "%3u", k);   /* { dg-bogus "may be truncated" "signed int" { xfail *-*-* } } */
 }
 
 void fulong (unsigned long j, char *p)
@@ -70,8 +70,7 @@  void fulong (unsigned long j, char *p)
   if (j > 999)
     return;
 
-  /* Range info isn't available here.  */
-  snprintf (p, 4, "%3lu", j);
+  snprintf (p, 4, "%3lu", j);   /* { dg-bogus "may be truncated" "unsigned long" { xfail *-*-* } } */
 }
 
 void flong (long j, char *p)
@@ -81,8 +80,7 @@  void flong (long j, char *p)
   if (k > 999)
     return;
 
-  /* Range info isn't available here.  */
-  snprintf (p, 4, "%3lu", k);
+  snprintf (p, 4, "%3lu", k);   /* { dg-bogus "may be truncated" "signed long" { xfail *-*-* } } */
 }
 
 void fullong (unsigned long long j, char *p)
@@ -90,18 +88,17 @@  void fullong (unsigned long long j, char *p)
   if (j > 999)
     return;
 
-  /* Range info isn't available here.  */
-  snprintf (p, 4, "%3llu", j);
+  snprintf (p, 4, "%3llu", j);   /* { dg-bogus "may be truncated" "signed long" { xfail *-*-* } } */
 }
 
-void fllong (long j, char *p)
+void fllong (long long j, char *p)
 {
   const unsigned long long k = (unsigned long long) j;
 
   if (k > 999)
     return;
 
-  snprintf (p, 4, "%3llu", k);
+  snprintf (p, 4, "%3llu", k);   /* { dg-bogus "may be truncated" "unsigned long long" { xfail *-*-* } } */
 }
 
 /* { dg-final { scan-tree-dump-not "abort" "optimized" } } */