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