clang-tidy

Cppcheck is a nice and useful static code analyser, but clang-tidy is definitely worth mentioning as an alternative.

It is part of the clang toolchain that compiles C (clang) and C++ (clang++) code against the LLVM backend. clang is still not as popular on HPC systems as the GNU and Intel families of compilers, but for debugging, it can be a valuable tool.

The warnings generated by clang and clang++ are pretty thorough. To get to know everything the compilers can warn about, you can use a single flag -Weverything (easy to remember too). However, don't use this in production, as you may get warnings that are inconsistent with other command line options. The -Weverything is in fact only intended for compiler developers.

clang-tidy is a very useful tool, part of the clang toolchain, and can detect additional problems that the compilers won't warn about.

A drawback is that to use clang-tidy, you first have to create a compilation database. This is easy if CMake is your build tool. Since an introduction to CMake is out of scope for this course, there are two options: 1. you are familiar with CMake, just read on; 1. you are not familiar with CMake, feel free to treat this as optional material, and skip it.

How do you set it up?

If CMake is your build tool, then generating a compilation database for clang-tidy is trivial:

$ cmake  -DCMAKE_EXPORT_COMPILER_COMMANDS=on  path/to/source

This generates a file compile_commands.json, the compilation database for the project.

What does it check?

clang-tidy implements many checks, and not all are useful in your context. To see which checks are enabled, simply run clang-tidy with the -list-checks options, i.e.,

$ clang-tidy  -list-checks

You will see that by default, only checks with the prefix clang-analyzer are enabled, in total some 80 checks at the time of writing.

However, many more checks can be enable in addition to these 80 core checks. To view all available checks, enable them all.

$ clang-tidy  -checks='*'  -list-checks

You will recognize check categories, * android: checks related to Android; not useful for HPC; * bugprone: checks that target bugprone code constructs; * cert: checks related to CERT Secure Coding Guidelines; * clang-analyzer: Clang Static Analyzer checks; * cppcoreguidelines: checks related to C++ Core Guidelines; * fuchsia: checks related to Fuchsia coding conventions; not useful for HPC; * google: checks related to Google style guide; * hicpp: checks related to High Integrity C++ Coding Standard; * llvm: checks related to the LLVM coding conventions; * misc: checks that they didn’t have a better category for; * modernize: checks that advocate usage of modern (currently “modern” means “C++11”) language constructs; * mpi: checks related to MPI (Message Passing Interface); * objc: checks related to Objective-C coding conventions; not useful for HPC, unless you use Objective-C; * performance: checks that target performance-related issues; * readability: checks that target readability-related issues that don’t relate to any particular coding style.

Some of these categories are quite useful to catch bugs as you can guess by their names, e.g., bugprone, clang-analyzer. Other categories focus more on coding style, e.g., google and readability. Yet others are only useful in a very specific context, e.g., android if you happen to develop for the Android platform, or mpi for distributed parallel programming using an MPI library.

Enabling checks is based on globbing, e.g., to enable the readability checks, you would use -checks='read*'. Note that the clang-analyzer checks are enabled as well.

The simplest approach is to enable all checks, i.e., -check='*'. However, this may take some time for large projects, and some tests are certainly useless for your project.

Although you could specify the checks explicitly on the command line, that would be error prone and inconvenient. A more convenient approach is to use a configuration file. clang-tidy can generate one for you that you can modify later, e.g., to have a configuration file that enables the readability checks, use

$ clang-tidy  -checks='read*'  -dump-config

The output looks like the following

--
Checks:          'clang-diagnostic-*,clang-analyzer-*,read*'
WarningsAsErrors: ''
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle:     none
User:            gjb
CheckOptions:
  - key:             google-readability-braces-around-statements.ShortStatementLines
    value:           '1'
  - key:             google-readability-function-size.StatementThreshold
    value:           '800'
...

Saving this output into a file .clang-tidy will ensure that this configuration is used when you run clang-tidy. By modifying the Checks line, you can enable additional checks.

How does it report issues?

The output clang-tidy generates is very similar to that of a compiler, e.g.,

$ clang-tidy  array.c
1 warning generated.
/home/gjb/training-material/Debugging/ClangTidy/array.c:8:11: warning: The left operand of '>' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult]
   if (m > n) {
         ^
/home/gjb/training-material/Debugging/ClangTidy/array.c:7:5: note: 'm' declared without an initial value
   int m;
   ^
/home/gjb/training-material/Debugging/ClangTidy/array.c:8:11: note: The left operand of '>' is a garbage value
   if (m > n) {
         ^

As with all verification tools, it pays to use them from the start of your project. It can be both intimidating and depressing to be confronted with clang-tidy output for a large code base.

Can it do more?

Indeed, actually clang-tidy can fix your code in some circumstances. Caveat emptor, it is an automatic tool, so I would advice using it with care. For instance, using the -fix option on the example above would insert braces around the body of the for-loop.

$ clang-tidy  -fix  -checks='google*'  array.c