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]>
---
v2:
fix va_list detection
increase test coverage
---
gcc/c-family/c-format.cc | 88 +++++++++++++++++++++++++++
gcc/testsuite/gcc.dg/format/pr61898.c | 78 ++++++++++++++++++++++++
2 files changed, 166 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..6b2c3238b 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,93 @@ 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;
+
+ 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..c8c392f47
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/pr61898.c
@@ -0,0 +1,78 @@
+/* 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;
+
+ /* 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'" } */
+}
+
+
--
2.43.0