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