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
-Wformat
then misbehaviour is possible. -
At the point that the check runs, the AST contains a single
StringLiteral
for 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.h
header 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
/fprintf
becomesfmt::print
- rewrite the format string to use the {fmt} format language
- wrap any arguments that corresponded to
%p
specifiers 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.