Custom support for __attribute__((format))
c, c++, clang, gcc, printf
Solution
One year and a half after having asked this question, I came out with a totally different approach to solve the real problem: Is there any way to statically check the types of custom variadic formatting statements?
For completeness and because it can help other people, here is the solution I have finally implemented. It has two advantages over the original question:
- Relatively simple : implemented in less than a day;
- Compiler independent : can check C++ code on any platform (Windows, Android, OSX, ...).
A Perl script parses the source code, finds the formatting strings and decodes the percent modifiers inside them. It then wraps all arguments with a call to a template identity function `CheckFormat<>`. Example:
str->appendFormat("%hhu items (%.2f %%) from %S processed",
nbItems,
nbItems * 100. / totalItems,
subject);
Becomes:
str->appendFormat("%hhu items (%.2f %%) from %S processed",
CheckFormat<CFL::u, CFM::hh>(nbItems ),
CheckFormat<CFL::f, CFM::_>(nbItems * 100. / totalItems ),
CheckFormat<CFL::S, CFM::_, const BaseString*>(subject ));
The enumerations `CFL`, `CFM` and the template function `CheckFormat` must be defined in a common header file like this (this is an extract, there are around 24 overloads).
enum class CFL
{
c, d, i=d, star=i, u, o=u, x=u, X=u, f, F=f, e=f, E=f, g=f, G=f, p, s, S, P=S, at
};
enum class CFM
{
hh, h, l, z, ll, L=ll, _
};
template<CFL letter, CFM modifier, typename T> inline T CheckFormat(T value) { CFL test= value; (void)test; return value; }
template<> inline const BaseString* CheckFormat<CFL::S, CFM::_, const BaseString*>(const BaseString* value) { return value; }
template<> inline const BaseObject* CheckFormat<CFL::at, CFM::_, const BaseObject*>(const BaseObject* value) { return value; }
template<> inline const char* CheckFormat<CFL::s, CFM::_, const char*>(const char* value) { return value; }
template<> inline const void* CheckFormat<CFL::p, CFM::_, const void*>(const void* value) { return value; }
template<> inline char CheckFormat<CFL::c, CFM::_, char>(char value) { return value; }
template<> inline double CheckFormat<CFL::f, CFM::_, double>(double value) { return value; }
template<> inline float CheckFormat<CFL::f, CFM::_, float>(float value) { return value; }
template<> inline int CheckFormat<CFL::d, CFM::_, int>(int value) { return value; }
...
After having the compilation errors, it is easy to recover the original form with a regular expression `CheckFormat<[^<]*>\((.*?) \)` replaced by its capture.
Problem
Both GCC and Clang have a support to make compile-time checks on variable argument functions like `printf`. These compilers accept syntax like: ``` extern void dprintf(int dlevel, const char *format, ...) __attribute__((format(printf, 2, 3))); /* 2=format 3=params */ ``` On OSX, the Cocoa framework also use an extension of this for `NSString`: ``` #define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A))) ``` In our company, we have a custom C++ framework with a bunch of classes like `BaseString` all deriving from `BaseObject`. In `BaseString` there are a few variable argument methods similar to `sprintf`, but with some extensions. For example, `"%S"` expects an argument of type `BaseString*`, and `"%@"` expects a `BaseObject*` argument. I would like to perform a compile-time check of the arguments in our projects, but because of the extensions, `__attribute__((format(printf)))` give lots of false positive warnings. Is there a way to customize the support of `__attribute__((format))` for one of the two compilers ? If this requires a patch to the compiler source, is it doable in a reasonable amount of time ? Alternatively, are there other lint like tools that could perform the check ?