OCLint

Writing Custom Rules

It’s cool to add capability to OCLint by writing your own rules, instead of waiting for us get around to implementing them. You are more welcome to share your rules with the entire community.

Note

It might be easier to get started with looking at existing rules.

Rules must implement RuleBase class or its derived abstract classes. Different rules are specialized in different abstract levels, for example, some rules may have to dig very deep into the control flow of the code, and on the contrary, some rules only detect the defects by reading the string of the source code. Rules module internals can help pick up the right category when writing rules. Here, we skip that discussion, and directly jump to these different types of rules.

Generic Rules

Writing a generic rule, we need to implement RuleBase interface.

We name the new rule and set the priority to it. Name will be shown in the report along with the violations. Priority stands for how severe when something breaks the rule. Priorities are defined in number, the less number has a higher priority, meaning, people needs to pay more attention to them.

RuleBase only provides a RuleCarrier, which carries the context of the abstract syntax tree (ASTContext) and a violation set. The ASTContext already have all syntactic information we need for analysis, and we need to figure out the way ourselves. We write our logic in apply method.

When we find the nodes we need in the ASTContext, throw those nodes into the violation set inside RuleCarrier.

For all rules, we use static constructor to register the rules. A static attribute is a variable that has been allocated statically, the lifetime of static variable extends across the entire execution of the program. So, each rule implementation has a static member rules, and we need to make sure our rule can be collected by it when being loaded.

Writing generic rules is very flexible, we can do everything we want from the ASTContext we have. However, since the most of our rules can reuse certain logic to have the work done better, so we recommend the new rules inherent from the abstract classes described below instead of using RuleBase directly. Don’t worry of losing the flexibility, all abstract classes are subclasses of RuleBase, so when we need this type of flexibility, we can still have it.

In addition, certain methods are still pure in these abstract classes, like name and priority methods. Plus the static RuleSet constructor, we still need to implement them in all rules. [Thinking about the paragraph to make it go smoother to the next paragraph.] Now, let’s look at some abstract rule classes.

Source Code Reader Rules

AbstractSourceCodeReaderRule provides a eachLine method. We can have the text of each line and the current line number. We can then work around with the text. For example, we can calculate the length of the text, we can understand if it is a comment, we can find out if there is a mix use of spaces and tabs, and so on.

AST Visitor Rules

The majority of the existing rules inherit AbstractASTVisitorRule class. It follows the visitor pattern. In our rules, we only write methods for the type of nodes that we are interested. For example, when we want to inspect all if statement, we should write

bool VisitIfStmt(IfStmt *ifStmt)
{
    // do stuff with this if statement
    return true;
}

Similarly, when we analyze an Objective-C method, we can visit that node by

bool VisitObjCMethodDecl(ObjCMethodDecl *decl)
{
    // analyze decl
    return false;
}

The return boolean value of these visit methods are used to control the traversal. AST visitor will continue its sub nodes or sibling nodes when visiting the current node return a true, vice versa, it stops when current visit method returns a false.

See also

We are using the abstract syntax tree generated by Clang, so understanding the API of Clang AST is very helpful when writing rules. There are some useful links that we have assembled together in Related Clang Knowledge Base page.

We also have a setUp method and a tearDown method in case we need to prepare certain stuff ready before the analysis and wrap up results afterwards. setUp method is guaranteed to be called before visiting any of the AST nodes, and after the AST analysis is done, tearDown is called for sure.

AST Matcher Rules

If possible, we are more willing to write AST matcher rules (unless performance is a big issue), in order to achieve the great readability that comes along with the AST matcher. We always prefer simple and concise.

All AST matcher rules inherit from AbstractASTMatcherRule class.

We need to add all matchers in setUpMatcher method. With each matcher, we bind a unique (within the scope of current rule) name for that in matcher.

Then whenever a match is found, callback method is called with that AST node as a parameter. So we can get that node with the name we have binded in previous step. We then add this node with other information into violation set.

See also

Again, LibASTMatcher is provided by Clang, and we would like to suggest you by reading some related Clang knowledge to have a better understanding.

Creating Rules with Scaffolding

Rules scaffolding is a quick way to create custom rules. When we want to create our custom rules and build them along with the OCLint building pipeline, scaffolding is the tool for the job.

We can tell the category, rule type, name, and priority to the scaffold script, or we can leave them with default settings.

Read on rule scaffolding document for details.

Build it and Make it Live

After coding for our new rule, now we have our new rule ready. We need to compile it into a dynamic library and link against LLVMSupport, clangASTMatchers, OCLintMetric, OCLintUtil, and OCLintCore libraries. We also have a CMake macro build_dynamic_rule to ease this process.

We copy the new dynamic library into $(/path/to/bin/oclint)/../lib/oclint/rules, and it will be loaded together with all other rules in this folder.

Unit Testing

We have a series of convenient methods for rules’ unit testing. They are testRuleOnCode method for regular C code, testRuleOnCXXCode method for C++ code, and testRuleOnObjCCode method to test Objective-C code. By giving the code we want to apply the rule on and our expectation result, this method will parse the code and run only current rule, and compare the expectation. It fails the test when the rule doesn’t meet expecting behaviors. A quick sample usage is like this

TEST(BitwiseOperatorInConditionalRuleTest, BitwiseOrInWhile)
{
    testRuleOnCode(new BitwiseOperatorInConditionalRule(), "void m() { while (1 | 0) {;} }", 0, 1, 19, 1, 23);
    // testRuleOnCode(
    //     new RuleToBeTested(),
    //     "source code",
    //     violationIndex,
    //     expectStartLine,
    //     expectStartColumn,
    //     expectEndLine,
    //     expectEndColumn,
    //     optionalExpectMessage);
    // When we expect the code has no violation, simple write
    // testRuleOnCode(new RuleToBeTested(), "source code");
}