Passing a va_list to a variadic function like printf() instead of its
v-variant (vprintf()) is a common programming error that can lead to
undefined behavior. This adds a warning to detect such cases.

The warning uses a multi-tiered detection approach:
1. First tries the canonical is_va_list_type() function
2. Falls back to exact name matching for typedef'd va_list from stdarg.h
3. Handles array-to-pointer decay by checking the pointed-to type
4. Uses exact string comparison against known system va_list type names
   to avoid false positives with user-defined types

The warning message is intentionally generic to work with any function
that has a format attribute, whether standard library functions or
user-defined functions.

        PR c/61898
gcc/c-family/ChangeLog:

        * c-format.cc: Include tree-sra.h for is_va_list_type.
        (check_format_info): Add detection and warning for va_list
        arguments passed to variadic functions.

gcc/testsuite/ChangeLog:

        * gcc.dg/format/pr61898.c: New test.

Signed-off-by: Osama Abdelkader <[email protected]>
---
v3:
make warning more generic
add more test coverage
v2:
fix va_list detection
increase test coverage
---
 gcc/c-family/c-format.cc              | 71 ++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/format/pr61898.c | 80 +++++++++++++++++++++++++++
 2 files changed, 151 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..0ad99ed5a 100644
--- a/gcc/c-family/c-format.cc
+++ b/gcc/c-family/c-format.cc
@@ -38,6 +38,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attribs.h"
 #include "c-family/c-type-mismatch.h"
 #include "tree-pretty-print-markup.h"
+#include "tree-sra.h"
 
 /* Handle attributes associated with format checking.  */
 
@@ -1631,6 +1632,76 @@ 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);
+                 
+                 /* Check if this is a va_list type.  We need to handle the 
case where
+                    va_list from stdarg.h may be a typedef that's not 
identical to
+                    va_list_type_node.  */
+                 bool is_va_list = false;
+                 
+                 /* First, try the standard is_va_list_type function */
+                 if (is_va_list_type (param_type))
+                   is_va_list = true;
+                 
+                 /* If that fails, check if it's a va_list-related type by 
examining
+                    the type name.  This handles the case where va_list from 
stdarg.h
+                    is a typedef to a system type like __va_list_tag.  */
+                 if (!is_va_list)
+                   {
+                     /* Handle pointer types (array decay case) */
+                     tree check_type = param_type;
+                     if (TREE_CODE (param_type) == POINTER_TYPE)
+                       check_type = TREE_TYPE (param_type);
+
+                     if (TYPE_NAME (check_type))
+                       {
+                         tree type_name = TYPE_NAME (check_type);
+                         const char *name_str = NULL;
+
+                         if (TREE_CODE (type_name) == IDENTIFIER_NODE)
+                           name_str = IDENTIFIER_POINTER (type_name);
+                         else if (TREE_CODE (type_name) == TYPE_DECL)
+                           {
+                             tree decl_name = DECL_NAME (type_name);
+                             if (decl_name)
+                               name_str = IDENTIFIER_POINTER (decl_name);
+                           }
+
+                         /* Check if this is a va_list-related type.  We only 
check for
+                            exact system type names to avoid false positives 
with
+                            user-defined types that happen to contain 
"va_list".  */
+                         if (name_str && (strcmp (name_str, "va_list") == 0
+                                          || strcmp (name_str, 
"__va_list_tag") == 0
+                                          || strcmp (name_str, 
"__builtin_va_list") == 0))
+                           is_va_list = true;
+                       }
+                   }
+                 
+                 if (is_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;
+                     
+                       warning_at (param_loc, OPT_Wformat_,
+                                   "passing %<va_list%> as a variadic 
argument; "
+                                   "consider using the corresponding v-variant 
function "
+                                   "instead (e.g., %<vprintf%> instead of 
%<printf%>)");
+                   }
+               }
+           }
+         
          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..bdc9abf02
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/pr61898.c
@@ -0,0 +1,80 @@
+/* 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);
+}
+
+/* Test that user-defined types with "va_list" in the name don't trigger false 
positives */
+typedef struct my_va_list_struct {
+  int dummy;
+} my_va_list_struct;
+
+void 
+test_user_type (const char *fmt, ...)
+{
+  my_va_list_struct user_va_list;
+  printf (fmt, user_va_list);  /* { dg-bogus "passing 'va_list'" } */
+}
+
+/* Additional test cases for user-defined types */
+typedef struct {
+  int x;
+} my_va_list;  /* Name contains "va_list" but it's not the system type */
+
+typedef int va_list_count;  /* Name contains "va_list" but it's not the system 
type */
+
+typedef void *custom_va_list_ptr;  /* Name contains "va_list" but it's not the 
system type */
+
+void 
+test_user_types_no_false_positives (const char *fmt, ...)
+{
+  my_va_list list1;
+  va_list_count count;
+  custom_va_list_ptr ptr;
+  void *void_ptr;
+  
+  /* None of these should trigger the warning */
+  printf (fmt, list1);   /* { dg-bogus "passing 'va_list'" } */
+  printf (fmt, count);   /* { dg-bogus "passing 'va_list'" } */
+  printf (fmt, ptr);     /* { dg-bogus "passing 'va_list'" } */
+  printf (fmt, void_ptr); /* { dg-bogus "passing 'va_list'" } */
+}
+
+
-- 
2.43.0

Reply via email to