User support __ attribute __ ((format))

Both GCC and Clang have support for performing compile-time checks on variable argument functions, such as 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 uses the extension for NSString :

 #define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A))) 

In our company, we have a custom C ++ infrastructure with a bunch of classes, such as BaseString , all from BaseObject . BaseString has several variable argument methods, similar to sprintf , but with some extensions. For example, "%S" expects an argument of type BaseString* , and "%@" expects an argument of BaseObject* .

I would like to check the parameters at compile time in our projects, but due to the extensions __attribute__((format(printf))) gives a lot of false positive warnings.

Is there a way to configure __attribute__((format)) support for one of the two compilers? If this requires a patch to the compiler source, is this possible in a reasonable amount of time? Alternatively, are there other tools that can perform validation?

+6
source share
4 answers

A year and a half after I asked this question, I had a completely different approach to solving the real problem: is there a way to statically check the types of custom formatting variables?

For completeness and because it can help other people, here is a solution that I finally implemented. It has two advantages over the original question:

  • Relatively simple: implemented in less than a day;
  • Compiler Independent: You can test C ++ code on any platform (Windows, Android, OSX, ...).

The Perl script parses the source code, finds the format strings, and decodes the percent modifiers inside them. Then it wraps all the arguments with a call to the CheckFormat<> template identification function. 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 )); 

CFL , CFM and CheckFormat template CheckFormat must be defined in a common header file like this (this is an extract, there are about 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 compilation errors, it is easy to restore the original form by replacing the regular expression CheckFormat<[^<]*>\((.*?) \) its capture.

+2
source

With the recent version of GCC (I recommend 4.7 or later, but you can try with GCC 4.6), you can add your own attributes for variables and functions through the GCC plugin (using the PLUGIN_ATTRIBUTES hook) or MELT . MELT is a domain-specific language for the GCC extension (implemented as the [meta-] plugin).

If you use a plugin (e.g. MELT), you will not need to recompile the GCC source code. But you need a plugin with GCC enabled (check with gcc -v ).

Some Linux distributions do not include plugins in their gcc - please contact the distribution provider; others provide a package for developing a GCC plugin, for example. gcc-4.7-plugin-dev for Debian or Ubuntu.

+5
source

This is doable, but it is certainly not easy; part of the problem is that BaseString and BaseObject are user-defined types, so you need to dynamically define format specifiers. Fortunately, gcc at least supports this, but still requires a compiler fix.

The magic is in the handle_format_attribute function in gcc/c-family/c-format.c , which calls the initialization functions for format specifiers that are related to user types. A good example to support your support would be the gcc_gfc format gcc_gfc , as it defines the %L format specifier for locus * :

 /* This will require a "locus" at runtime. */ { "L", 0, STD_C89, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "R", NULL }, 

Obviously, you want to base the format_char_info array on print_char_table , as this defines the standard printf specifiers; gcc_gfc significantly reduced in comparison.

The patch added by gcc_gfc , http://gcc.gnu.org/ml/fortran/2005-07/msg00018.html ; From this patch it should be fairly obvious how and where you will need to make your additions.

+2
source

With C ++ 11, you can solve this problem by replacing __attribute__ ((format)) clever combination of constexpr , decltype and variational parameter packages. Pass the format string to the constexpr function, which retrieves all % specifiers at compile time and checks that the n'th specifier matches the decltype (n + 1) 'st argument.

+1
source

Source: https://habr.com/ru/post/944108/


All Articles