Skip to content

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. Note: the repository does not accept github pull requests at this moment. Please submit your patches at http://reviews.llvm.org.

License

Notifications You must be signed in to change notification settings

mikecrowe/clang-tidy-fmt

 
 

Repository files navigation

clang-tidy fmt checks

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" and "ab" "cd" will become "abcd".) 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.

  • Use of any unsupported flags or specifiers will cause the entire statement to be left alone. Known unsupported features are:

    • The % flag for thousands separators. It looks like this could be translated to {:L}, but I'm not sure it will do exactly the same thing.

    • The glibc extension %m. This could be supported relatively easily if we can assume that strerror is thread safe (which the glibc version is.)

  • 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.

Usage:

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

How it works

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 becomes fmt::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 to fmt::ptr.

Will it work for my printf-like function too?

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.

fmt-strprintf-convert

The fmt-strprintf-convert check converts calls to a commonly-implemented sprintf wrapper function strprintf(const char *format, ...) that is expected to return std::string to the equivalent fmt::format call. For example, it turns:

const std::string s = strprintf("%d:%s", 42, "hello");

into:

const std::string s = fmt::format("{}:{}", 42, "hello");

fmt-trace-convert

The fmt-trace-convert check converts calls to operator()(const char *format, ...) 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.)

Complementary checks

Once the calls have been converted to use fmt::format or fmt::print, the range of types that can be passed is greatly increased. In particular, any std::string parameters that were being converted to C-style strings by calling std::string::c_str() would be better passed as is. In fact, it may even be more efficient to do so in some circumstances since it won't be necessary to walk the string to determine its length. This tree also contains improvements to the readability-redundant-string-cstr check to detect such arguments to fmt::print and fmt::format.

Running the LIT tests

Once you've built everything, run something like:

bin/llvm-lit -v ../clang-tools-extra/test/clang-tidy/checkers/fmt/printf-convert.cpp
bin/llvm-lit -v ../clang-tools-extra/test/clang-tidy/checkers/fmt/strprintf-convert.cpp
bin/llvm-lit -v ../clang-tools-extra/test/clang-tidy/checkers/fmt/trace-convert.cpp
bin/llvm-lit -v ../clang-tools-extra/test/clang-tidy/checkers/readability/redundant-string-cstr.cpp

The Future

The {fmt} library has gradually been standardised. fmt::format is in C++20 as std::format and fmt::print is in C++23 as std::print. It would not be hard to adapt these checks to convert to the standard versions instead.

About

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. Note: the repository does not accept github pull requests at this moment. Please submit your patches at http://reviews.llvm.org.

Resources

License

Security policy

Stars

Watchers

Forks

Packages