Custom static analysis with Cppcheck

A simple use case

While working on some code, I noticed a disturbing pattern: std::endl followed by std::flush.

For those that do not know it, std::endl flushes the buffer, so doing std::flush does not make any sense, unless you want to pessimize your program.

As the newline character normally already flushes the buffer (unless something like tie and/or sync_with_stdio has been used), it would thus make even more sense to almost always replace std::endl with '\n'.

If the code is nicely formatted, a simple grep would find most instances of std::endl << std::flush. Of course, it might have some false positives (like comments), and it would miss some occurrences (if the expression is split into multiple lines or is formatted differently).

It is not perfect but gets the job done.

The biggest downside is the integration with the development process. A grep from the command line is a one-off shot, I wanted a solution that would have prevented, or help to prevent, this pattern to pop up again.

Static analysis with the compiler

Thus I checked on a simple test program like the following

#include <iostream>

int main() {
	std::cout << "Hello World" << std::endl << std::flush;
}

if Clang (tested version 8.0.1-3 with -Weverything), GCC (version 9, with -Wall -Wextra) or cl.exe (version 19.21.27702.2, with /Wall) would emit any diagnostic.

I might have missed the warning with cl.exe, as because of the header iostream the output was more than 1000 lines long, but it seemed to me that no compiler warned about this pattern.

Cppcheck

Cppcheck is a cross-platform C and C++ static code analysis tool. As it is free software, it should be in the toolbox of any developer, and ideally integrated into any development process. If you are using CMake as of version 3.10, then you can integrate it into your build system without any hassle. There are also plugins for qtcreator, kdevelop, Visual studio and surely other for IDEs.

Unfortunately cppcheck --enable=all flush.cpp triggered no warning (tested version 1.89).

Fortunately, it is pretty straightforward to create custom rules with regular expressions, as described in the manuals.

As explained in the documentation, the easiest approach is to begin with cppcheck --rule=".+" flush.cpp. It shows how the file is transformed when the regular expression is applied to it.

The output is

int main ( ) { std :: cout << "Hello World" << std :: endl << std :: flush ; }

As Cppcheck unifies the formatting, writing the regular expression is much easier.

A first working attempt:

cppcheck --rule="std :: endl << std :: flush" flush.cpp

emits the desired diagnostic:

[flush.cpp:4]: (style) found 'std :: endl << std :: flush'

For simpler reuse, it is possible to write it down to a rule file.

<?xml version="1.0"?>
<rule version="1">
	<pattern>std :: endl &lt;&lt; std :: flush</pattern>
	<message>
		<id>redundantCondition</id>
		<severity>performance</severity>
	<summary>Multiple flushes. Consider flushing only once, and replacing "std::endl" with "\n"</summary>
	</message>
</rule>

Obviously there are other minor patterns that could make sense, to check, for example std::flush << std::endl, std::flush << std::flush and std::endl << std::endl.

AFAIK the rule file does not permit defining multiple rules, but it’s possible to make the regular expression a little more complex

<?xml version="1.0"?>
<rule version="1">
	<pattern>std :: (endl|flush) &lt;&lt; std :: (endl|flush)</pattern>
	<message>
		<id>redundantCondition</id>
		<severity>performance</severity>
	<summary>Multiple flushes. Consider flushing only once, and replacing "std::endl" with "\n"</summary>
	</message>
</rule>

Now it can be used like cppcheck --rule-file=flush.rule flush.cpp.

Conclusion

Regular expressions are normally not the best tool for parsing and verifying code. This regular expression, without cppcheck, would have failed if someone would have used using namespace std; and flush without the std:: qualifier. Thanks to Cppcheck also this and probably many more scenarios works, as this and other expression are normalized, and for this check, parsing the AST, which would permit a contextual search instead of string search, is probably an overkill.

As already mentioned, there are already many possibilities for integrating cppcheck with many other tools, thus the custom check too without too much hassle.