Clang provides a user-friendly framework for writing basic static-analysis checks. In this post we will see how analysis on the clang abstract syntax tree (AST) can be used to implement powerful checks for single translation units pretty easily.
- The problem
- Setting up needed tools
- Adding scaffolding
- Anatomy of a checker
- A matcher for virtual methods
- Testing the check
- Processing of base classes
As an example we will write a static analysis check which can catch the following problematic code:
B defines a virtual method
B::f with a name identical to a
member function in its base class
A which was never intended to be customized
(i.e. it was not declared as
This will create the confusing situation that depending on the type the member function was called through different functions will be called, e.g.
We will write a check that catches the problematic method declaration in
Setting up needed tools
We will write our check as an extension of the
tool which is part of clang.
If we have a compilation
database for our
source we can perform a check with
clang-tidy by running in the build
directory (assuming we have a compilation database)
or on any source file, even without compilation databases, but with no automatic support for custom build flags
Since we will be working directly inside the clang tool sources we need to check out and build the upstream sources.
Make sure you add the
bin/ directory of that build to your path, e.g.
With that we are ready to add our custom check.
clang-tidy sources are in
tools/clang/tools/extra/clang-tidy inside the
LLVM source tree.
Let’s change to the
clang-tidy source directory and add our check (which we
will aptly call
We will add our tool to the
[misc] category of
clang-tidy checks and before
anything else will need to create the needed files and integrate them into the
build. Thankfully there is a tool doing all of that for us:
This will create
misc/VirtualShadowingCheck.cpp, and additionally include it in
misc/MiscTidyModule.cpp so it can be run as a normal part of
clang-tools-extra svn revision
236309 (git commit
6a5bbb2) we still
need to modify
misc/CMakeLists.txt so that our newly added dependency
VirtualShadowingCheck.cpp comes before the
We can now create a version of
clang-tidy including our checker by rebuilding
llvm and tools. To run it on some code we would run
Here we have first disabled all default-enabled checks with
-* and then
exclusively enabled our check. Right now running this does not output too much
Anatomy of a checker
misc/VirtualShadowingCheck.h we find
VirtualShadowingCheck is our custom check defined inside the
clang::tidy namespace. It derives from
ClangTidyCheck. We will need to provide implementations for two functions,
check (remove their dummy implementations for the time being):
registerMatcherswe register clang AST matchers to filter out intesting source locations, and
checkwe provide a function which is called by the clang machinery whenever a match was found; we can perform further actions here (e.g. emit a warning).
In our case we want to check for any
virtual method of some class whether any
of the class’ bases defined a method with the same name, and our implementation
strategy will be
- filter out declarations of any virtual method with a matcher registered in
- walk the bases of the matched class in
checkand compare to the matched
A matcher for virtual methods
Clang comes with a large set of basic matchers for many use cases. Chaining them allows creating powerful matchers.
To get a feeling for the kind of AST node representing
B::f looking at the
clang AST is helpful,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 % clang-check -ast-dump problematic.cpp -- TranslationUnitDecl 0x2b3cd20 <<invalid sloc>> <invalid sloc> |-TypedefDecl 0x2b3d258 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128' |-TypedefDecl 0x2b3d2b8 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128' |-TypedefDecl 0x2b3d698 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list '__va_list_tag ' |-CXXRecordDecl 0x2b3d6e8 </media/sf_home_host/test.cpp:1:1, line:3:1> line:1:8 referenced struct A definition | |-CXXRecordDecl 0x2b3d800 <col:1, col:8> col:8 implicit struct A | `-CXXMethodDecl 0x2b3d8e0 <line:2:9, col:19> col:14 f 'void (void)' | `-CompoundStmt 0x2b3d9b8 <col:18, col:19> `-CXXRecordDecl 0x2b3d9d0 <line:5:1, line:7:1> line:5:8 struct B definition |-public 'struct A' |-CXXRecordDecl 0x2b85050 <col:1, col:8> col:8 implicit struct B |-CXXMethodDecl 0x2b85100 <line:6:3, col:21> col:16 f 'void (void)' virtual | `-CompoundStmt 0x2b854f8 <col:20, col:21> |-CXXMethodDecl 0x2b85208 <line:5:8, <invalid sloc>> col:8 implicit operator= 'struct B &(const struct B &)' inline noexcept-unevaluated 0x2b85208 | `-ParmVarDecl 0x2b85330 <col:8> col:8 'const struct B &' `-CXXDestructorDecl 0x2b853b8 <col:8> col:8 implicit ~B 'void (void)' inline noexcept-unevaluated 0x2b853b8
We see the two classes defined in lines 6 and 10 as
encode both classes and structs; the two definitions of
f in lines 8 and 13
CXXMethodDecls which encapsulate (not much suprisingly)
declarations of methods in C++.
The filter we need would first need to catch method declarations and then then
refine that to only methods declared
virtual. As first filter we use the
which finds 4
CXXMethodDecls (two for our explicitly declared methods
f and two for implicitly declared methods).
We chain that with the
isVirtual matcher to only match virtual methods,
which finds just the method we are interested in.
All left for us to do is to update the definition of
so it matches
virtual method declarations,
This binds the identifier
method to the found method (note its placement with respect to the parentheses).
Testing the check
If you have built
you will have seen that the dummy test case added by
fails so we should update it to at least correctly reflect our intended use
Here we have added three test cases:
- the line marked problematic is our initial problem and should trigger a
warning. We have specified the location of the expected warning with the
CHECK-MESSAGESmacro: the warning should be on the next line (
+1) on column 3. We specified the full expected warning text.
- the lines marked OK(1) and OK(2) should not trigger the warning since
they represent valid use cases; consequentlially we added no
We can already add the diagnostic message to
If we rerun the test suite we will see that there is still some work left,
Processing of base classes
The matcher we registered will call
VirtualShadowingCheck::check whenever a
matching AST node was found. There we can retrieve the matches by name with
Result.Nodes.getNodeAs<CXXMethodDecl> returns a
is valid as long as the translation unit is loaded (i.e. much longer than
check is running).
We can already stop processing if the class containing
method has no bases,
Rerunning the test suite shows that now case OK(1) is removed, but we still match OK(2).
To check the base classes for non-virtual methods with identical names we need
to walk the tree of bases; clang provides infrastructure to perform that walk
BaseMatches is a callback with the signature
CXXRecordDecl pointing to the declaration of a base
UserData can point to something we could use to pass additional
information along1. With
AllowShortCircuit = true the callback will be called
for all bases as long the callback returns
false all bases would be walked. If the class has no bases
We can use this to recursively walk the tree of bases. We will pass a pointer
Userdata to perform checks on the name. Inside
VirtualShadowingCheck::check we would call
i.e. call some predicate
CandidatePred for all bases of the class containing
method and give up if none of them returns true.
If this would not exit
check we emit a warning for
method since it collides
with some base’s name. With this
VirtualShadowingCheck::check’s declaration would look like
Now all the work left is to define the predicate.
Rerunning the test suite show that we have covered all our test cases,
While we have just scratched the surface of what is possible, it has never been easier to write custom static analysis checks of C++ code. Clang provides both infrastructure and tools which allow the user to focus on the real problem – formulating the problem.
In the development version clang-3.8 the signature changed and
bool CXXRecordDecl::forallBases(ForallBasesCallback *BaseMatches, bool AllowShortCircuit = true)
ForallBasesCallbackis a callable taking a
const CXXRecordDecl*as only argument. To pass additional data along one can explicitly (and in a type-safe manner) capture data with a lambda closure. ↩