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 helped to prevent, this pattern from popping 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 << 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) << 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 work, as this and other expressions 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 also the custom check without too much hassle.
Do you want to share your opinion? Or is there an error, some parts that are not clear enough?
You can contact me anytime.