PR c/88993 - GCC 9 -Wformat-overflow=2 should reflect real libc limits
gcc/ChangeLog:
PR c/88993
* gimple-ssa-sprintf.c (sprintf_dom_walker::call_info::is_file_func):
New helper.
(sprintf_dom_walker::call_info::is_string_func): New helper.
(format_directive): Only issue "may exceed" 4095/INT_MAX warnings
for formatted string functions.
(sprintf_dom_walker::handle_gimple_call): Fix a typo in a comment.
gcc/testsuite/ChangeLog:
PR c/88993
* gcc.dg/tree-ssa/builtin-fprintf-warn-2.c: New test.
* gcc.dg/tree-ssa/builtin-printf-warn-2.c: New test.
* testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-3.c: Adjust.
===================================================================
@@ -943,6 +943,29 @@ 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 fncodetions. */
+ 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'). */
@@ -2847,7 +2870,9 @@ format_directive (const sprintf_dom_walker::call_i
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 (),
@@ -2855,17 +2880,21 @@ format_directive (const sprintf_dom_walker::call_i
"minimum required size of 4095", dirlen,
target_to_host (hostdir, sizeof hostdir, dir.beg),
fmtres.range.min);
- else
+ else if (!minunder4k)
warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
- 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"),
+ "%<%.*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? */
@@ -2885,26 +2914,32 @@ format_directive (const sprintf_dom_walker::call_i
&& maxximax
&& fmtres.range.max < HOST_WIDE_INT_MAX)))
{
- /* The directive output causes the total length of output
- to exceed INT_MAX bytes. */
+ /* The directive output or either causes or may cause the total
+ length of output to exceed INT_MAX bytes. */
- if (fmtres.range.min == fmtres.range.max)
+ if (likelyximax && 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
+ else if (fmtres.range.min > target_int_max ())
warned = fmtwarn (dirloc, argloc, NULL, info.warnopt (),
- 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%>"), dirlen,
+ "%<%.*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)
@@ -3842,7 +3877,7 @@ sprintf_dom_walker::handle_gimple_call (gimple_stm
case BUILT_IN_PRINTF_CHK:
// Signature:
- // __builtin_printf_chk (it, format, ...)
+ // __builtin_printf_chk (ost, format, ...)
idx_format = 1;
info.argidx = 2;
idx_dstptr = -1;
===================================================================
@@ -0,0 +1,159 @@
+/* PR middle-end/88993 - GCC 9 -Wformat-overflow=2 should reflect real
+ libc limits
+ Verify that -Wformat-overflow=2 "may exceed" warnings are not issued
+ for printf family of functions.
+ { dg-do compile }
+ { dg-options "-O -Wformat -Wformat-overflow=2 -ftrack-macro-expansion=0" }
+ { dg-require-effective-target int32plus } */
+
+#define INT_MAX __INT_MAX__
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef struct FILE FILE;
+
+FILE *fp;
+
+#define T(...) __builtin_fprintf (__VA_ARGS__)
+
+/* Exercise the "%c" directive with constant arguments. */
+
+void test_printf_c_const (int width)
+{
+ /* Verify that a "may exceed" warning is only issued when the output
+ is definitely exceeded but not when exceeding it is possible but
+ not inevitable. */
+ T (fp, "%*c", INT_MAX, '1'); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T (fp, "%*c", 4096, '1'); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T (fp, "X%*c", 4095, '1');
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T (fp, "%*c", width, '1');
+ T (fp, "%*cX", width, '1');
+ T (fp, "%*c%*c", width, '1', width, '2');
+ T (fp, "X%*cY%*cZ", width, '1', width, '2');
+
+ if (width < 4096)
+ width = 4096;
+
+ T (fp, "%*c", width, '1'); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
+
+
+/* Exercise the "%s" directive with constant arguments. */
+
+void test_printf_s_const (int width)
+{
+ const char *s = "";
+
+ /* Verify that output in excess of INT_MAX bytes is diagnosed even
+ when the size of the destination object is unknown. */
+ T (fp, "%*s", INT_MAX, s); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T (fp, "%*s", 4096, s); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T (fp, "X%*s", 4095, s);
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T (fp, "%*s", width, s);
+ T (fp, "%*sX", width, s);
+ T (fp, "%*s%*s", width, s, width, s);
+ T (fp, "X%*sY%*sZ", width, s, width, s);
+
+ if (width < 4096)
+ width = 4096;
+
+ T (fp, "%*s", width, s); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
+
+/* Exercise the "%ls" directive with constant arguments. */
+
+void test_printf_ls_const (int width)
+{
+ const wchar_t *ls = L"";
+
+ /* Verify that output in excess of INT_MAX bytes is diagnosed even
+ when the size of the destination object is unknown. */
+ T (fp, "%*ls", INT_MAX, ls); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T (fp, "%*ls", 4096, ls); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T (fp, "X%*ls", 4095, ls);
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T (fp, "%*ls", width, ls);
+ T (fp, "%*lsX", width, ls);
+ T (fp, "%*ls%*ls", width, ls, width, ls);
+ T (fp, "X%*lsY%*lsZ", width, ls, width, ls);
+
+ if (width < 4096)
+ width = 4096;
+
+ T (fp, "%*ls", width, ls); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
+
+
+/* Also exercise fprintf_chk. */
+
+#undef T
+#define T(...) __builtin___fprintf_chk (__VA_ARGS__)
+
+void test_printf_chk_s_const (int width)
+{
+ const char *s = "0123456789";
+
+ /* Verify that output in excess of INT_MAX bytes is diagnosed even
+ when the size of the destination object is unknown. */
+ T (fp, 0, "%*s", INT_MAX, s); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T (fp, 0, "%*s", 4096, s); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T (fp, 0, "X%*s", 4095, s);
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T (fp, 0, "%*s", width, s);
+ T (fp, 0, "%*sX", width, s);
+ T (fp, 0, "%*s%*s", width, s, width, s);
+ T (fp, 0, "X%*sY%*sZ", width, s, width, s);
+
+ if (width < 4096)
+ width = 4096;
+
+ T (fp, 0, "%*s", width, s); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
+
+
+/* And finally exercise fprintf_unlocked. */
+
+#undef T
+#define T(...) __builtin_fprintf_unlocked (__VA_ARGS__)
+
+void test_printf_unlocked_s_const (int width)
+{
+ const char *s = "0123456789";
+
+ /* Verify that output in excess of INT_MAX bytes is diagnosed even
+ when the size of the destination object is unknown. */
+ T (fp, "%*s", INT_MAX, s); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T (fp, "%*s", 4096, s); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T (fp, "X%*s", 4095, s);
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T (fp, "%*s", width, s);
+ T (fp, "%*sX", width, s);
+ T (fp, "%*s%*s", width, s, width, s);
+ T (fp, "X%*sY%*sZ", width, s, width, s);
+
+ if (width < 4096)
+ width = 4096;
+
+ T (fp, "%*s", width, s); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
===================================================================
@@ -0,0 +1,170 @@
+/* PR middle-end/88993 - GCC 9 -Wformat-overflow=2 should reflect real
+ libc limits
+ Verify that -Wformat-overflow=2 "may exceed" warnings are not issued
+ for printf family of functions.
+ { dg-do compile }
+ { dg-options "-O -Wformat -Wformat-overflow=2 -ftrack-macro-expansion=0" }
+ { dg-require-effective-target int32plus } */
+
+/* When debugging, define LINE to the line number of the test case to exercise
+ and avoid exercising any of the others. */
+#ifndef LINE
+# define LINE 0
+#endif
+
+#define INT_MAX __INT_MAX__
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+int dummy_printf (const char*, ...);
+
+
+/* Helper to expand function to either __builtin_f or dummy_f to
+ make debugging GCC easy. */
+#define T(...) \
+ (((!LINE || LINE == __LINE__) \
+ ? __builtin_printf : dummy_printf) (__VA_ARGS__))
+
+/* Exercise the "%c" directive with constant arguments. */
+
+void test_printf_c_const (int width)
+{
+ /* Verify that a "may exceed" warning is only issued when the output
+ is definitely exceeded but not when exceeding it is possible but
+ not inevitable. */
+ T ("%*c", INT_MAX, '1'); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T ("%*c", 4096, '1'); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T ("X%*c", 4095, '1');
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T ("%*c", width, '1');
+ T ("%*cX", width, '1');
+ T ("%*c%*c", width, '1', width, '2');
+ T ("X%*cY%*cZ", width, '1', width, '2');
+
+ if (width < 4096)
+ width = 4096;
+
+ T ("%*c", width, '1'); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
+
+
+/* Exercise the "%s" directive with constant arguments. */
+
+void test_printf_s_const (int width)
+{
+ const char *s = "";
+
+ /* Verify that output in excess of INT_MAX bytes is diagnosed even
+ when the size of the destination object is unknown. */
+ T ("%*s", INT_MAX, s); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T ("%*s", 4096, s); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T ("X%*s", 4095, s);
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T ("%*s", width, s);
+ T ("%*sX", width, s);
+ T ("%*s%*s", width, s, width, s);
+ T ("X%*sY%*sZ", width, s, width, s);
+
+ if (width < 4096)
+ width = 4096;
+
+ T ("%*s", width, s); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
+
+/* Exercise the "%ls" directive with constant arguments. */
+
+void test_printf_ls_const (int width)
+{
+ const wchar_t *ls = L"";
+
+ /* Verify that output in excess of INT_MAX bytes is diagnosed even
+ when the size of the destination object is unknown. */
+ T ("%*ls", INT_MAX, ls); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T ("%*ls", 4096, ls); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T ("X%*ls", 4095, ls);
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T ("%*ls", width, ls);
+ T ("%*lsX", width, ls);
+ T ("%*ls%*ls", width, ls, width, ls);
+ T ("X%*lsY%*lsZ", width, ls, width, ls);
+
+ if (width < 4096)
+ width = 4096;
+
+ T ("%*ls", width, ls); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
+
+
+/* Also exercise printf_chk. */
+
+#undef T
+#define T(...) __builtin___printf_chk (__VA_ARGS__)
+
+void test_printf_chk_s_const (int width)
+{
+ const char *s = "0123456789";
+
+ /* Verify that output in excess of INT_MAX bytes is diagnosed even
+ when the size of the destination object is unknown. */
+ T (0, "%*s", INT_MAX, s); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T (0, "%*s", 4096, s); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T (0, "X%*s", 4095, s);
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T (0, "%*s", width, s);
+ T (0, "%*sX", width, s);
+ T (0, "%*s%*s", width, s, width, s);
+ T (0, "X%*sY%*sZ", width, s, width, s);
+
+ if (width < 4096)
+ width = 4096;
+
+ T (0, "%*s", width, s); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
+
+
+/* And finally exercise printf_unlocked. */
+
+#undef T
+#define T(...) \
+ (((!LINE || LINE == __LINE__) \
+ ? __builtin_printf_unlocked : dummy_printf) (__VA_ARGS__))
+
+void test_printf_unlocked_s_const (int width)
+{
+ const char *s = "0123456789";
+
+ /* Verify that output in excess of INT_MAX bytes is diagnosed even
+ when the size of the destination object is unknown. */
+ T ("%*s", INT_MAX, s); /* { dg-warning "directive output of \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+ T ("%*s", 4096, s); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
+ T ("X%*s", 4095, s);
+
+ if (width > INT_MAX - 1)
+ width = INT_MAX - 1;
+
+ T ("%*s", width, s);
+ T ("%*sX", width, s);
+ T ("%*s%*s", width, s, width, s);
+ T ("X%*sY%*sZ", width, s, width, s);
+
+ if (width < 4096)
+ width = 4096;
+
+ T ("%*s", width, s); /* { dg-warning "directive output between 4096 and \[0-9\]+ bytes exceeds minimum required size of 4095" } */
+}
===================================================================
@@ -166,11 +166,17 @@ void test_string_checked (const char *s, const str
T (-1, "%s%s", ar->a4k, ar->ax);
/* Verify that an array that fits a string longer than 4095 bytes
- does trigger a warning. */
- T (-1, "%-s", ar->a4kp1); /* { dg-warning "directive output between 0 and 4096 bytes may exceed minimum required size of 4095" } */
+ does not trigger a warning. (No known implementation has trouble
+ with this). */
+ T (-1, "%s", ar->a4kp1);
- /* Also verify that a %s directive with width greater than 4095
- triggers a warning even if the argument is not longer than 4k. */
+ /* Verify that a %s directive with width greater than 4095 does
+ trigger a warning even if the string argument is not longer
+ than 4k. Glibc only has trouble with directives whose width
+ or precision exceeds 64K or so:
+ https://bugzilla.redhat.com/show_bug.cgi?id=441945 *
+ but hardcoding that as the limit and assuming no other
+ implementation has a lower one seems unwise. */
T (-1, "%*s", 4096, ar->a4k); /* { dg-warning "directive output of 4096 bytes exceeds minimum required size of 4095" } */
/* Verify that precision constrains the putput and suppresses the 4k
@@ -190,5 +196,7 @@ void test_string_checked (const char *s, const str
T (-1, "%s %s %s", ar->a4k, ar->a4k, ar->a4k);
T (-1, "%s %s %s", ar->ax, ar->ax, ar->ax);
- T (-1, "%-s", ar->amax); /* { dg-warning "directive output between 0 and \[0-9\]+ bytes may exceed minimum required size of 4095" } */
+ /* Similar to the above, verify there's no warning for an array
+ just because its size is INT_MAX bytes. */
+ T (-1, "%s", ar->amax);
}