Passing a va_list object to a variadic function (like printf instead
of vprintf) results in undefined behavior, but GCC did not previously
warn about this common mistake.
This patch adds a warning when a non-literal format string is used
with variadic format functions and one of the arguments is a va_list
type. The warning suggests using the appropriate v-variant function.
gcc/c-family/ChangeLog:
PR c/61898
* c-format.cc (check_format_info): Add check for va_list arguments
passed to variadic format functions. Suggest using v-variant
functions (vprintf, vscanf, etc.) instead.
gcc/testsuite/ChangeLog:
PR c/61898
* gcc.dg/format/pr61898.c: New test.
Signed-off-by: Osama Abdelkader <[email protected]>
---
gcc/c-family/c-format.cc | 66 +++++++++++++++++++++++++++
gcc/testsuite/gcc.dg/format/pr61898.c | 43 +++++++++++++++++
2 files changed, 109 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/format/pr61898.c
diff --git a/gcc/c-family/c-format.cc b/gcc/c-family/c-format.cc
index bf144d0f6..76ec2e9db 100644
--- a/gcc/c-family/c-format.cc
+++ b/gcc/c-family/c-format.cc
@@ -1631,6 +1631,72 @@ check_format_info (function_format_info *info, tree
params,
params = TREE_CHAIN (params);
++arg_num;
}
+
+ /* Check if any of the arguments is a va_list. This is almost
+ always wrong - the user likely meant to use the v-variant
+ (e.g., vprintf instead of printf). */
+ if (params)
+ {
+ tree param_value = TREE_VALUE (params);
+ if (param_value)
+ {
+ tree param_type = TREE_TYPE (param_value);
+
+ /* va_list can be an array type (like on x86_64:
__va_list_tag[1]),
+ which decays to a pointer when passed as an argument.
+ Dereference pointer types to check the underlying type. */
+ tree check_type = param_type;
+ if (TREE_CODE (param_type) == POINTER_TYPE)
+ check_type = TREE_TYPE (param_type);
+
+ tree type_name = TYPE_NAME (check_type);
+ const char *type_name_str = NULL;
+
+ if (type_name)
+ {
+ if (TREE_CODE (type_name) == IDENTIFIER_NODE)
+ type_name_str = IDENTIFIER_POINTER (type_name);
+ else if (TREE_CODE (type_name) == TYPE_DECL)
+ {
+ tree decl_name = DECL_NAME (type_name);
+ if (decl_name)
+ type_name_str = IDENTIFIER_POINTER (decl_name);
+ }
+ }
+
+ if (type_name_str && strstr (type_name_str, "va_list"))
+ {
+ location_t param_loc = UNKNOWN_LOCATION;
+ if (arglocs && arg_num <= arglocs->length ())
+ param_loc = (*arglocs)[arg_num - 1];
+ if (param_loc == UNKNOWN_LOCATION)
+ param_loc = loc;
+
+ const char *func_name =
format_types[info->format_type].name;
+ /* Suggest the v-variant function name. Handle both
+ "printf" and "gnu_printf" style names. */
+ const char *v_func_name = func_name;
+ if (strncmp (func_name, "gnu_", 4) == 0)
+ v_func_name = "vprintf";
+ else if (strncmp (func_name, "ms_", 3) == 0)
+ v_func_name = "vprintf";
+ else if (strncmp (func_name, "printf", 6) == 0)
+ v_func_name = "vprintf";
+ else if (strncmp (func_name, "scanf", 5) == 0)
+ v_func_name = "vscanf";
+ else if (strncmp (func_name, "strftime", 8) == 0)
+ v_func_name = "vstrftime";
+ else
+ v_func_name = func_name; /* Fallback */
+
+ warning_at (param_loc, OPT_Wformat_,
+ "passing %<va_list%> as a variadic argument; "
+ "did you mean to use %<%s%> instead?",
+ v_func_name);
+ }
+ }
+ }
+
if (params == 0 && warn_format_security)
warning_at (loc, OPT_Wformat_security,
"format not a string literal and no format arguments");
diff --git a/gcc/testsuite/gcc.dg/format/pr61898.c
b/gcc/testsuite/gcc.dg/format/pr61898.c
new file mode 100644
index 000000000..ec4dd72ff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/pr61898.c
@@ -0,0 +1,43 @@
+/* Test for PR c/61898: warn when passing va_list to variadic functions. */
+/* { dg-do compile } */
+/* { dg-options "-Wformat" } */
+
+#include <stdarg.h>
+#include <stdio.h>
+
+void
+test_printf (const char *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ printf (fmt, ap); /* { dg-warning "passing 'va_list' as a variadic
argument" } */
+ va_end (ap);
+}
+
+void
+test_fprintf (const char *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ fprintf (stderr, fmt, ap); /* { dg-warning "passing 'va_list' as a variadic
argument" } */
+ va_end (ap);
+}
+
+void
+test_vprintf (const char *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ vprintf (fmt, ap); /* { dg-bogus "passing 'va_list'" } */
+ va_end (ap);
+}
+
+void
+test_vfprintf (const char *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ vfprintf (stderr, fmt, ap); /* { dg-bogus "passing 'va_list'" } */
+ va_end (ap);
+}
+
--
2.43.0