[PATCH][gcc] Allow functions without C-style ellipsis to use format attribute

Tuan Le Quang lequangtuan6b@gmail.com
Mon Jun 28 04:24:45 GMT 2021


Hi,

Currently, format attribute can be used to do type-checking for arguments
with respect to  a format string. However, only functions with a C-style
ellipsis can use it.
Supporting this attribute for non-variadic functions(functions without a
C-style ellipsis) gives nice convenience especially when writing code in
C++, we can use it for C++ variadic template functions like this

template<Args...args>
__attribute__((format(printf, 1, 2))) void myPrint (const char * fmt,
Args...args)

This patch will introduce these changes:
1. It is no longer an error simply to have a function with the format
attribute but no C-style variadic arguments
2. Functions are subjected to warnings/errors as before, except errors
mentioned in point 1 about not being variadic. For example, when a
non-variadic function has wrong arguments, e.g
__attribute__((format(printf, 1, 1))) or when being type-checked.

Note that behaviours of C-style variadic functions do not change, errors
and warnings are given as before.

This patch does it by:
1.   Relaxing several conditions for format attribute:
     -  Will only use POSARG_ELLIPSIS flag to call `get_constant` when
getting attribute arguments of a variadic function
     -  Relax the check for the last argument of the attribute (will not
require an ellipsis argument)
     -  (Before this patch) After passing the above check, current gcc will
call `get_constant` to get the function parameter that the third attribute
argument is pointing to. If POSARG_ELLIPSIS is set, `get_constant` will
look for `...`. If not, `get_constant` will look for a C-style string. Note
that POSARG_ELLIPSIS is set automatically for getting the third attribute
argument.
        (After this patch) POSARG_ELLIPSIS is set only when the function
has C-style '...'. Now, if POSARG_ELLIPSIS is not set, `get_constant` will
not check whether the third argument of format attribute points to a
C-style string.
2.   Modifying expected outcome of a testcase in objc testsuite, where we
expect a warning instead of an error
3.   Adding 2 test files

Successully bootstrapped and regression tested on x86_64-pc-linux-gnu.

Signed-off-by: Le Quang Tuan <lequangtuan6b@gmail.com>

gcc/c-family/ChangeLog:

* c-attribs.c (positional_argument): allow third argument of format
attribute to point to parameters of any type if the function is not C-style
variadic
* c-format.c (decode_format_attr): read third argument with POSARG_ELLIPSIS
only if the function has has a variable argument
(handle_format_attribute): relax explicit checks for non-variadic functions

gcc/testsuite/ChangeLog:

* gcc.dg/format/attr-3.c: modify comment
* objc.dg/attributes/method-format-1.m: errors do not hold anymore, a
warning is given instead
* g++.dg/warn/format9.C: New test with usage of variadic templates.
* gcc.dg/format/attr-9.c: New test.

diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 6bf492afcc0..7a17ce671de 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -714,6 +714,11 @@ positional_argument (const_tree fntype, const_tree
atname, tree pos,
   return NULL_TREE;
  }

+  /* For format attribute with argno >= 3, we don't expect any type
+   */
+  if (argno >= 3 && strcmp (IDENTIFIER_POINTER(atname), "format") == 0 &&
!(flags & POSARG_ELLIPSIS ) )
+    return pos;
+
       /* Where the expected code is STRING_CST accept any pointer
  expected by attribute format (this includes possibly qualified
  char pointers and, for targets like Darwin, also pointers to
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index bda3b18fcd0..453565ad7b8 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -380,9 +380,15 @@ decode_format_attr (const_tree fntype, tree atname,
tree args,
   else
     return false;

+  bool has_variable_arg = !type_argument_type(fntype,
type_num_arguments(fntype) + 1);
+  int extra_flag = 0;
+  if (has_variable_arg) {
+    extra_flag = POSARG_ELLIPSIS;
+  }
+
   if (tree val = get_constant (fntype, atname, *first_arg_num_expr,
        3, &info->first_arg_num,
-       (POSARG_ZERO | POSARG_ELLIPSIS), validated_p))
+       (POSARG_ZERO | extra_flag), validated_p))
     *first_arg_num_expr = val;
   else
     return false;
@@ -5193,11 +5199,11 @@ handle_format_attribute (tree *node, tree atname,
tree args,
   tree arg_type;

   /* Verify that first_arg_num points to the last arg,
-     the ...  */
+     if the last arg is  ... */
   FOREACH_FUNCTION_ARGS (type, arg_type, iter)
     arg_num++;

-  if (arg_num != info.first_arg_num)
+  if (arg_num != info.first_arg_num && !type_argument_type(type, arg_num))
     {
       if (!(flags & (int) ATTR_FLAG_BUILT_IN))
  error ("argument to be formatted is not %<...%>");
diff --git a/gcc/testsuite/g++.dg/warn/format9.C
b/gcc/testsuite/g++.dg/warn/format9.C
new file mode 100644
index 00000000000..39b615859fc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/format9.C
@@ -0,0 +1,16 @@
+// Test format attribute used with variadic templates
+// { dg-do compile { target c++11 } }
+// { dg-options "-Wformat" }
+
+template<typename...Args>
+__attribute__((format(printf, 1, 2))) void fa (const char * fmt,
Args...args)
+{
+    return;
+}
+
+int main()
+{
+    fa ("%d%d", 5, 7);
+    fa ("%d%d%d", 5, 6, 7);
+    fa ("%s%d", 3, 4); // { dg-warning "format" "printf warning" }
+}
diff --git a/gcc/testsuite/gcc.dg/format/attr-3.c
b/gcc/testsuite/gcc.dg/format/attr-3.c
index 1b275c5aba8..64990c43472 100644
--- a/gcc/testsuite/gcc.dg/format/attr-3.c
+++ b/gcc/testsuite/gcc.dg/format/attr-3.c
@@ -55,7 +55,7 @@ extern void fg2 () __attribute__((format(gnu_attr_printf,
1, 1))); /* { dg-error
 extern void fg3 () __attribute__((format(gnu_attr_printf, 2, 1))); /* {
dg-error "follows" "bad number order" } */

 /* The format string argument must be a string type, and the arguments to
-   be formatted must be the "...".  */
+   be formatted must be the "..." if the function is variadic  */
 extern void fh0 (int, ...) __attribute__((format(gnu_attr_printf, 1, 2)));
/* { dg-error ".format. attribute argument 2 value .1. refers to parameter
type .int." "format int string" } */
 extern void fh1 (signed char *, ...)
__attribute__((format(gnu_attr_printf, 1, 2))); /* { dg-error ".format.
attribute argument 2 value .1. refers to parameter type .signed char \\\*."
"signed char string" } */
 extern void fh2 (unsigned char *, ...)
__attribute__((format(gnu_attr_printf, 1, 2))); /* { dg-error ".format.
attribute argument 2 value .1. refers to parameter type .unsigned char
\\\*." "unsigned char string" } */
diff --git a/gcc/testsuite/gcc.dg/format/attr-9.c
b/gcc/testsuite/gcc.dg/format/attr-9.c
new file mode 100644
index 00000000000..3f6788f291c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/attr-9.c
@@ -0,0 +1,21 @@
+/* Test for format attributes: test usage of the attribute for
non-variadic functions */
+/* Origin: Tuan Le <lequangtuan6b@gmail.com> */
+/* { dg-do compile } */
+/* { dg-options "-std=gnu99 -Wformat" } */
+
+#define DONT_GNU_PROTOTYPE
+#include "format.h"
+
+/* Usage of the attributes */
+extern void fa (const char *, int, const char *)
__attribute__((format(gnu_attr_printf, 1, 2)));
+extern void fb (const char *, int, int, int)
__attribute__((format(gnu_attr_printf, 1, 3)));
+
+/* Some bad uses */
+extern void fc (const char *, int) __attribute__((format(gnu_attr_printf,
1, 1))); /* { dg-error "format string argument follows the arguments to be
formatted" } */
+
+void
+foo (const char *s, int *p)
+{
+    fa ("%d%s", 5, "hello");
+    fa ("%d %d", 5, "hello"); /* { dg-warning "format" "attribute format
__printf__" } */
+}
diff --git a/gcc/testsuite/objc.dg/attributes/method-format-1.m
b/gcc/testsuite/objc.dg/attributes/method-format-1.m
index d3bb997b2aa..443f9c73120 100644
--- a/gcc/testsuite/objc.dg/attributes/method-format-1.m
+++ b/gcc/testsuite/objc.dg/attributes/method-format-1.m
@@ -20,8 +20,8 @@
 - (void) log2: (int)level  message: (const char *) my_format, ...
 __attribute__ ((format (printf, 2)));    /* { dg-error "wrong" } */
 + (void) debug2: (const char *) my_format, ...  __attribute__ ((format
(printf))); /* { dg-error "wrong" } */
 - (void) debug2: (const char *) my_format, ...  __attribute__ ((format
(printf))); /* { dg-error "wrong" } */
-+ (void) alert: (const char *) my_format __attribute__ ((format (printf,
1, 2))); /* { dg-error "does not refer to a variable argument list" } */
-- (void) alert: (const char *) my_format __attribute__ ((format (printf,
1, 2))); /* { dg-error "does not refer to a variable argument list" } */
++ (void) alert: (const char *) my_format __attribute__ ((format (printf,
1, 2))); /* { dg-warning "exceeds the number of function parameters" } */
+- (void) alert: (const char *) my_format __attribute__ ((format (printf,
1, 2))); /* { dg-warning "exceeds the number of function parameters" } */
 @end

 void test (LogObject *object)
-- 
2.17.1


More information about the Gcc-patches mailing list