This "fork" of llvm-project adds a proof-of-concept clang-tidy checker
that converts occurrences of printf and fprintf to fmt::print (as
provided by the {fmt} library) and modifies the format string
appropriately. In other words, it turns:
fprintf(stderr, "The %s is %3d\n", answer, value);into:
fmt::print(stderr, "The {} is {:3}\n", answer, value);It doesn't do a bad job, but it's not perfect. In particular:
-
It assumes that the input is mostly sane. If you get any warnings when compiling with
-Wformatthen misbehaviour is possible. -
At the point that the check runs, the AST contains a single
StringLiteralfor the format string and any macro expansion, token pasting, adjacent string literal concatenation and escaping has been handled. Although it's possible for the check to automatically put the escapes back, they may not be exactly as they were written (e.g. "\x0a" will become "\n".) It turns out that it's probably quite important that macro expansion and adjacent string literal concatenation happen before we parse the format string in order to cope with the <inttypes.h> PRI macros. -
It tries to support field widths, precision, positional arguments, leading zeros, leading +, alignment and alternative forms.
-
It is assumed that the
fmt/format.hheader has already been included. No attempt is made to include it. -
It has some tests in clang-tools-extra/test/clang-tidy/checkers/fmt-printf-convert.cpp but they probably don't cover the full set of possibilities.
-
It copes with calls to printf, ::printf and std::printf. Unfortunately this means that it also changes mine::printf which is probably incorrect. My attempts to fix this using isInStdNamespace() have failed.
-
This is my first attempt at a clang-tidy checker, so it's probably full of things that aren't done the idiomatic LLVM way.
-
It's not separated into easily-understandable commits with good commit messages yet.
Build clang-tidy following the upstream instructions. Install it if you wish, or just run from the build directory with something like:
bin/clang-tidy -checks='-*,fmt-printf-convert' --fix input.cpp
There are no clang-tidy checks for fmt yet, so I've added
clangTidyFmtModule. The FormatStringConverter class makes use of
Clang's own ParsePrintfString to walk the format string deciding what to
do. If the format string can be converted then PrintfConvertCheck simply
needs to replace printf or fprintf with fmt::print, and tell
FormatStringConverter to apply the necessary fixes. The applied fixes are:
printf/fprintfbecomesfmt::print- rewrite the format string to use the {fmt} format language
- wrap any arguments that corresponded to
%pspecifiers that {fmt} won't deal with in a call tofmt::ptr.
Maybe. In addition to the fmt-printf-convert check, there are two other
checks that are unlikely to be useful as they are, but they may be
modifiable to do what you want: fmt-strprintf-convert and
fmt-trace-convert.
The fmt-strprintf-convert check converts calls to a commonly-implemented
sprintf wrapper function that is expected to return std::string to the
equivalent fmt::format call. For example, it turns:
const std::string s = strprintf("%d", 42);into:
const std::string s = fmt::format("{}", 42);The fmt-trace-convert check converts calls to operator() on an object
that derives from a particular class (in this case, the class is
BaseTrace. For example, it converts:
class DerivedTrace : public BaseTrace {};
BaseTrace TRACE;
DerivedTrace TRACE2;
TRACE("%s=%d\n", name, value);
TRACE2("%s\n", name);into:
class DerivedTrace : public BaseTrace {};
BaseTrace TRACE;
DerivedTrace TRACE2;
TRACE("{}={}\n", name, value);
TRACE2("{}\n", name);(It is assumed that the implementation of BaseTrace will be modified at
the same time to expect the new form of format string.)
Once you've built everything, run something like:
bin/llvm-lit -v ../clang-tools-extra/test/clang-tidy/checkers/fmt-printf-convert.cpp
The {fmt} library is gradually being standardised. fmt::format is in
C++20 as std::format. It would not be hard to adapt these checks to
convert to the standard versions instead.