Clang Tooling I (add override keyword)

Chi, Chun Chen
4 min readApr 20, 2019

--

This article is about how to add “override” keyword to your C++ codebase by implementing a Clang Tooling tool and the program depends on version 8 of LLVM and Clang. Before start coding, I have to first introduce what is “override” keyword and why should we use this keyword.

Override is added into C++ since C++11 and it is for telling the compiler that a virtual function overrides another virtual function. The motivation of this override keyword is that sometimes programmer does not write the same function signature when he or she want to override a virtual function. A code snippet from geeksforgeeks.com explains the idea very well:

In this example, the compiler cannot detect (it compiles!) the mistake of the wrong function signature when the real intention of the programmer is to override the func function in derived class. For this reason, C++11 add override keyword and the compiler now could detect the error. The compiler error log can be found in the code snippet:

After realizing the motivation of override keyword in modern C++, we can now start explaining what LibTooling is and how to use it to implement a tool to refactor C++ code by adding override keyword.

LibTooling is a library for writing standalone tools based on Clang. For this article, I’ll not describe how to install Clang or LLVM, you can find those on the official website.

The first step of writing a LibTooling tool is to create and run the ClangTool instance. By reading the ClangTool documentation, you may notice that to create a ClangTool instance, we need to give it two arguments, one is CompilationDatabase, the other is SourcePaths. To get this two argument, we need to first create a CommonOptionsParser instance. It parses the arguments from the tool and require a OptionCategory instance. The OptionCategory class is for grouping the command line options of the tool (argc and argv). More details about OptionCategory could be found here. The extracted source code for this part:

After setting up ClangTool, we now need to run action over all the files specified in the command line, which happens inside ToolFactory class. The type of action for LibTooling is FrontendAction which is an abstract class that programmer implement each virutal function, such as BeginSourceFileAction, EndSourceFileAction, and CreateASTConsumer. BeginSourceFileAction callback is invoked before each file being processed by the compiler frontend, EndSourceFileAction callback is invoked after each file be processed by the compiler frontend, CreateASTConsumer is what we will implement for our tool which create an AST consumer for this action. The action class is an interface for programmers to define when to do things, while in the other side, the AST consumer is an interface for programmers to define where to do things (the scale of the AST). Now is the time to read some code about action and consumer:

Inside the Action class, you can see that three functions has been implemented and besides the part of the usage of the Rewriter instance, the code is pretty self-explained. The entry point of the Consumer class is in CreateASTConsumer. The function defined in Consumer class is HandleTranslationUnit which is called after the AST of the entire translation unit have been parsed, really simple, right?

Once finshed the skeleton of Consumer code, we now want to find the code where override should be inserted. For the pattern matching of the AST, clang has a very handy machinery for doing this. ASTMatcher allows progammer to write very simple code to find the pattern by writing DSL. Line 6–8 for the code snippet above matches every C++ member function and do not match any member function in the system header. Inside the Consumer class, a Checker instance is defined, which actually is a customed class derived from clang::ast_matchers::MatchFinder::MatchCallback. And for a derived callback class, it’s clear that we now need to implement the callback. The function of the callback is run:

Inside the callback (run), we first need to grab the AST node using the hook we added in Consumer class (line 9). And then check whether override keyword should be added for this AST node (needsOverride method). It first checks if the C++ member function is truely a overriden virtual function, and if it is, check if there is a override keyword in the source code. About the Clang Diagnostic code in the callback, I think it is pretty self-explanatory about the intention, but you can find more details in this blog post. The last thing to explain is how to find the location of where to insert override keyword (findInsertionPoint method). This function first find the location before the close braces of the function parameter list. For the example below, you can see the location pointed by ^:

class Base {
virtual bar () {
}
virtual foo(int x, int y) {
}
};class Derived {
virtual bar() {
^
} virtual foo(int x, int y) {
^
}};

We now have written the tool that could find out where to put override keyword! The usage of this tool is simple:

./use_override -rewrite test.cpp -- std=c++14

If the rewrite flag is removed, the tool use the Fixit hint instead of Rewriter.

--

--