fcloutier created this revision.
fcloutier added reviewers: dcoughlin, doug.gregor, rsmith.
fcloutier added a project: clang.
Herald added a reviewer: aaron.ballman.
fcloutier requested review of this revision.
Herald added a subscriber: cfe-commits.

Clang only allows you to use `__attribute__((format))` on variadic functions. 
There are legit use cases for `__attribute__((format))` on non-variadic 
functions, such as:

(1) variadic templates

  template<typename… Args>
  void print(const char *fmt, Args… &&args) __attribute__((format(1, 2))); // 
error: format attribute requires variadic function

(2) functions which take fixed arguments and a custom format:

  void print_number_string(const char *fmt, unsigned number, const char 
*string) __attribute__((format(1, 2)));
  // ^error: format attribute requires variadic function
  
  void foo(void) {
      print_number_string(“%08x %s\n”, 0xdeadbeef, “hello”);
      print_number_string(“%d %s”, 0xcafebabe, “bar”);
  }

This change allows Clang users to attach `__attribute__((format))` to 
non-variadic functions, including functions with C++ variadic templates. It 
replaces the error with a GCC compatibility warning and improves the type 
checker to ensure that received arrays are treated like pointers (this is a 
possibility in C++ since references to template types can bind to arrays).


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D112579

Files:
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/lib/AST/FormatString.cpp
  clang/lib/Sema/SemaDeclAttr.cpp
  clang/test/Sema/attr-format.c
  clang/test/Sema/attr-format.cpp

Index: clang/test/Sema/attr-format.cpp
===================================================================
--- /dev/null
+++ clang/test/Sema/attr-format.cpp
@@ -0,0 +1,16 @@
+//RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#include <stdarg.h>
+
+template<typename... Args>
+void format(const char *fmt, Args &&... args) // expected-warning{{GCC requires functions with 'format' attribute to be variadic}}
+    __attribute__((format(printf, 1, 2)));
+
+void do_format() {
+  int x = 123;
+  int &y = x;
+  const char *s = "world";
+  format("bare string");
+  format("%s %s %u %d %i %p\n", "hello", s, 10u, x, y, &do_format);
+  format("bad format %s"); // expected-warning{{more '%' conversions than data arguments}}
+}
Index: clang/test/Sema/attr-format.c
===================================================================
--- clang/test/Sema/attr-format.c
+++ clang/test/Sema/attr-format.c
@@ -5,7 +5,7 @@
 void a(const char *a, ...) __attribute__((format(printf, 1,2))); // no-error
 void b(const char *a, ...) __attribute__((format(printf, 1,1))); // expected-error {{'format' attribute parameter 3 is out of bounds}}
 void c(const char *a, ...) __attribute__((format(printf, 0,2))); // expected-error {{'format' attribute parameter 2 is out of bounds}}
-void d(const char *a, int c) __attribute__((format(printf, 1,2))); // expected-error {{format attribute requires variadic function}}
+void d(const char *a, int c) __attribute__((format(printf, 1, 2))); // expected-warning {{GCC requires functions with 'format' attribute to be variadic}}
 void e(char *str, int c, ...) __attribute__((format(printf, 2,3))); // expected-error {{format argument not a string type}}
 
 typedef const char* xpto;
@@ -39,7 +39,7 @@
 void a2(const char *a, ...)    __attribute__((format(printf0, 1,2))); // no-error
 void b2(const char *a, ...)    __attribute__((format(printf0, 1,1))); // expected-error {{'format' attribute parameter 3 is out of bounds}}
 void c2(const char *a, ...)    __attribute__((format(printf0, 0,2))); // expected-error {{'format' attribute parameter 2 is out of bounds}}
-void d2(const char *a, int c)  __attribute__((format(printf0, 1,2))); // expected-error {{format attribute requires variadic function}}
+void d2(const char *a, int c)  __attribute__((format(printf0, 1,2))); // expected-warning {{GCC requires functions with 'format' attribute to be variadic}}
 void e2(char *str, int c, ...) __attribute__((format(printf0, 2,3))); // expected-error {{format argument not a string type}}
 
 // FreeBSD usage
@@ -61,7 +61,7 @@
 void a3(const char *a, ...)    __attribute__((format(freebsd_kprintf, 1,2))); // no-error
 void b3(const char *a, ...)    __attribute__((format(freebsd_kprintf, 1,1))); // expected-error {{'format' attribute parameter 3 is out of bounds}}
 void c3(const char *a, ...)    __attribute__((format(freebsd_kprintf, 0,2))); // expected-error {{'format' attribute parameter 2 is out of bounds}}
-void d3(const char *a, int c)  __attribute__((format(freebsd_kprintf, 1,2))); // expected-error {{format attribute requires variadic function}}
+void d3(const char *a, int c)  __attribute__((format(freebsd_kprintf, 1,2))); // expected-warning {{GCC requires functions with 'format' attribute to be variadic}}
 void e3(char *str, int c, ...) __attribute__((format(freebsd_kprintf, 2,3))); // expected-error {{format argument not a string type}}
 
 
@@ -87,3 +87,9 @@
   __attribute__ ((__format__(__gcc_tdiag__, 1, 2)));
 
 const char *foo3(const char *format) __attribute__((format_arg("foo")));  // expected-error{{'format_arg' attribute requires parameter 1 to be an integer constant}}
+
+void call_nonvariadic(void) {
+  d3("%i", 123);
+  d3("%d", 123);
+  d3("%s", 123); // expected-warning{{format specifies type 'char *' but the argument has type 'int'}}
+}
Index: clang/lib/Sema/SemaDeclAttr.cpp
===================================================================
--- clang/lib/Sema/SemaDeclAttr.cpp
+++ clang/lib/Sema/SemaDeclAttr.cpp
@@ -3612,8 +3612,7 @@
     if (isFunctionOrMethodVariadic(D)) {
       ++NumArgs; // +1 for ...
     } else {
-      S.Diag(D->getLocation(), diag::err_format_attribute_requires_variadic);
-      return;
+      S.Diag(D->getLocation(), diag::warn_gcc_requires_variadic_function) << AL;
     }
   }
 
Index: clang/lib/AST/FormatString.cpp
===================================================================
--- clang/lib/AST/FormatString.cpp
+++ clang/lib/AST/FormatString.cpp
@@ -322,6 +322,12 @@
 
 clang::analyze_format_string::ArgType::MatchKind
 ArgType::matchesType(ASTContext &C, QualType argTy) const {
+  // When using the format attribute with variadic templates in C++, you can
+  // receive an array that will necessarily decay to a pointer when passed to
+  // the final format consumer. Apply decay before type comparison.
+  if (C.getAsArrayType(argTy))
+    argTy = C.getArrayDecayedType(argTy);
+
   if (Ptr) {
     // It has to be a pointer.
     const PointerType *PT = argTy->getAs<PointerType>();
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3045,8 +3045,6 @@
   "declared with index %0 here">;
 def err_format_strftime_third_parameter : Error<
   "strftime format attribute requires 3rd parameter to be 0">;
-def err_format_attribute_requires_variadic : Error<
-  "format attribute requires variadic function">;
 def err_format_attribute_not : Error<"format argument not %0">;
 def err_format_attribute_result_not : Error<"function does not return %0">;
 def err_format_attribute_implicit_this_format_string : Error<
@@ -4049,6 +4047,9 @@
 def warn_gcc_ignores_type_attr : Warning<
   "GCC does not allow the %0 attribute to be written on a type">,
   InGroup<GccCompat>;
+def warn_gcc_requires_variadic_function : Warning<
+  "GCC requires functions with %0 attribute to be variadic">,
+  InGroup<GccCompat>;
 
 // Clang-Specific Attributes
 def warn_attribute_iboutlet : Warning<
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to