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

Reply via email to