keywords

oclint, workflow, AST

Estimated reading time

10-15 min

What is OCLint

OCLint is a clang Tool-based static code analysis tool designed to improve quality and reduce defects by examining C, C ++, and Objective-C code and looking for potential problems (such as possible errors, unused code, complex code, redundant code, code smells, bad practices, etc.).

Current status

OCLint is still a long way from complete, but is improving in many areas, such as accuracy, performance and usability.

OCLint began as a research project at the University of Houston. Since then, OCLint has been rewritten and grown into a 100% open source project. OCLint is designed to serve academia and industry. The goal is to spread the word about the importance of code quality and make OCLint a must-have tool for developers programming in C, C ++, and Objective-C.

OCLint is compatible with macOS, major BSD and Linux variants. Porting to Windows was a community effort to experiment.

Basic usage

Analysis reports can be generated using Oclint, Oclint-JSON-compilation-database, and Oclint-XcodeBuild

  1. Usage of oclint command
Oclint [subcommand] [options] <source0> [... <sourceN>] -help- show available options (-help-hidden shows more) -help-list- Show list of available options (-help-list-hidden shows more) -version - Show OCLint options for the version of this program: -r =<directory> - Add the directory as the rule loading path - allow-headed - Violations - Allow duplicated violations in OCLint reporting -disable-rule=<rule name> - Disable the rule - enable-clang-static-Analyzer - Enable clang static analyzer to integrate results into OCLint reports -enable-global-analysis - compile each source and analyze it across global context (depending on the number of source files, May cause high memory load) -extra-arg=<string> - Extra arguments added to the end of the command line -extra-arg-before=<string> - Extra arguments added to the head of the command line -list-enabled-rules - List enabled rules -max-priority-1=<threshold> - Maximum number of priority 1 violations allowed -max-priority-2=<threshold> - Maximum number of priority 2 violations allowed -max-priority-3=<threshold> - Maximum number of allowed priority 3 violations -no-analytics - Anonymous analysis disabled -o=<path> - Specified path for report output -p=<string> - Compilation path -rc=<parameter>=<value> - Default behavior of rewriting rules -report-type=<name> - Modify type of output reports -rule=<rule name> - Explicitly select rules -p <build-path> Used to read the compile command databaseCopy the code
  1. The usage of the oclint-json-compilation-database command
Usage: oclint-json-compilation-database [-h] [-v] [-debug] [-i INCLUDES] [-e EXCLUDES] [oclint_args [oclint_args ...]] OCLint For JSON Compilation Database (compile_commands. JSON) Location parameters (Mandatory): The oclint_args parameter will be passed to the OCLint call Optional parameters: -h, --help displays the help message and exits -v displays the call command with parameters. -debug, --debug invokes OCLint in debug mode. -I INCLUDES, -include INCLUDES, -e EXCLUDES, -exclude EXCLUDES, --exclude EXCLUDES deletes matching file -p build-path Specifies the directory containing compile_commands.jsonCopy the code
  1. oclint-xcodebuild

Json file should be generated by running Oclint-XcodeBuild in the project root folder. For programmers using Xcode, it extracts build parameters from the Xcode build log to generate compile_commands.json. (additional xcpretty can directly generate JSONCompilationDatabase. Json)

Use rules

As mentioned earlier, Oclint's rules are in the form of dylib, and if the existing rules don't meet the business requirements, developers can write their own rules and compile into Dylib. Oclint provides us with 71 rules by default at installation time, which are in the lib/ Oclint /rules directory under the installation directory. Some rules allow you to set thresholds, such as row count detection (you can customize how many more rows are considered a violation). So how do you set the threshold?Copy the code

Here’s an example:

Reducing the number of lines to 50 gives the following command

-rc LONG_LINE=50
Copy the code

The full list of configurable parameters is provided here (Oclint version 20.11)

Source structure

In Oclint /driver, main.cpp serves as the entry point for the program. A simplified code framework for this file is shown below

int main(int argc, const char **argv) { llvm::cl::SetVersionPrinter(oclintVersionPrinter); CommonOptionsParser optionsParser(argc, argv, OCLintOptionCategory); / / configuration oclint: : option: : process (argv [0]). . / / structure analyzer oclint: : RulesetBasedAnalyzer analyzer (oclint: : option: : rulesetFilter () filteredRules ()); // Build driver oclint:: driver; / / execution analysis driver. The run (optionsParser getCompilations (), optionsParser. GetSourcePathList (), analyzer); std::unique_ptr<oclint::Results> results(std::move(getResults())); ostream *out = outStream(); Reporter ()->report(results.get(), *out); disposeOutStream(out); return handleExit(results.get()); }Copy the code

Looking at the key code snippet of the core Driver class, there are three more core methods

ConstructCompilers, Invoke, Run

Static void constructCompilers(STD ::vector<oclint::CompilerInstance *> &compilers, STD ::vector<oclint::CompilerInstance *> CompileCommandPairs &compileCommands, std::string &mainExecutable) { for (auto &compileCommand : // compileCommands {STD ::vector< STD ::string> adjustedCmdLine = adjustArguments(compileCommand.second.CommandLine, compileCommand.first); #ifndef NDEBUG printCompileCommandDebugInfo(compileCommand, adjustedCmdLine); #endif LOG_VERBOSE("Compiling "); LOG_VERBOSE(compileCommand.first.c_str()); std::string targetDir = stringReplace(compileCommand.second.Directory, "\\ ", " "); if(chdir(targetDir.c_str())) { throw oclint::GenericException("Cannot change dictionary into \"" + targetDir + "\", " "please make sure the directory exists and you have permission to access!" ); } clang::CompilerInvocation *compilerInvocation = newCompilerInvocation(mainExecutable, adjustedCmdLine); // Create the CompilerInstance invocation object oclint::CompilerInstance * Compiler = newCompilerInstance(CompilerInstance); // Create oclint's CompilerInstance using clang's CompilerInstance Invocation. Oclint does compiler->start(); // clang::FrontendAction takes the action and executes if (! compiler->getDiagnostics().hasErrorOccurred() && compiler->hasASTContext()) { LOG_VERBOSE(" - Success"); compilers.push_back(compiler); } else {LOG_VERBOSE(" -failed ");} else {LOG_VERBOSE(" -failed "); } LOG_VERBOSE_LINE(""); Static void invoke(CompileCommandPairs &compileCommands, STD ::string &mainExecutable, oclint::Analyzer &analyzer) { std::vector<oclint::CompilerInstance *> compilers; ConstructCompilers (compecommands, mainExecutable); Collect a collection of AST context STD ::vector<clang::ASTContext *> localcontext; For (auto compiler: compilers) // Traverses the compiler set {locallocalcontex.push_back (& Compiler ->getASTContext()); } // Use the analyzer to do the actual Analysis Analyzer.preprocess (localContexts); Preprocessing Analyzer.analyze (localContexts); analyzer.postprocess(localContexts); // send out the signals to release or simply leak resources for (size_t compilerIndex = 0; compilerIndex ! = compilers.size(); ++compilerIndex) { compilers.at(compilerIndex)->end(); delete compilers.at(compilerIndex); }} // the core method of the main.cpp call, Perform analysis of void Driver: : run (const clang: : tooling: : CompilationDatabase & CompilationDatabase, llvm::ArrayRef<std::string> sourcePaths, oclint::Analyzer &analyzer) { CompileCommandPairs compileCommands; ConstructCompileCommands (compileCommands, compilationDatabase, sourcePaths); Static int staticSymbol; / / static symbols STD: : string mainExecutable = LLVM: : sys: : fs: : getMainExecutable (" oclint ", & staticSymbol); / / get oclint executable path if (option: : enableGlobalAnalysis ()) {invoke (compileCommands mainExecutable, analyzer); } else { for (auto &compileCommand : compileCommands) { CompileCommandPairs oneCompileCommand { compileCommand }; invoke(oneCompileCommand, mainExecutable, analyzer); } } if (option::enableClangChecker()) { invokeClangStaticAnalyzer(compileCommands, mainExecutable); }}Copy the code

The last one is the RulesetBasedAnalyzer class, which has very little code, as shown below

void RulesetBasedAnalyzer::analyze(std::vector<clang::ASTContext *> &contexts) { for (const auto& context : contexts) { LOG_VERBOSE("Analyzing "); auto violationSet = new ViolationSet(); auto carrier = new RuleCarrier(context, violationSet); // The violationSet is used to store the processed result set LOG_VERBOSE(carrier->getMainFilePath().c_str()); For (RuleBase *rule: _filteredRules) // the commuter plane was waiting for... / / call the rules of takeoff} ResultCollector * results = ResultCollector: : getInstance (); Results ->add(violationSet); // Add rule processed data to collector LOG_VERBOSE_LINE(" -done "); }}Copy the code

From the code above it can be seen that Analyzer will traverse the rule set to call rule’s reach method. Rule’s base class is RuleBase. This base class contains an example RuleCarrier as a member. RuleCarrier contains the ASTContext and violationSet for each file. A violationSet is used to store information about a violation. The rule’s job is to check the ASTContext of its member variable, ruleCarrier, and write the result to ruleCarrier’s violationSet.

So, after a brief reading and analysis of the source code, we can get the following call diagram of Oclint’s core classes.

Advanced: User-defined rules

Above, we’ve looked at the basic usage and workflow of Oclint.

The next part that is more flexible and more difficult to use is custom rules.

Rules must implement the RuleBase class or an abstract class derived from it. Different rules are dedicated to different levels of abstraction; for example, some rules may have to delve very deeply into the control flow of code, whereas some rules detect defects simply by reading strings of source code.

Oclint provides three abstract classes for writing custom rules. AbstractSourceCodeReaderRule (source code reader rules), AbstractASTVisitorRule (AST visitor rules), and AbstractASTMatcherRule (AST matcher rules).

According to the official documentation, unless performance is a big issue, we will probably choose to write AST matcher rules most of the time because of the readability of AST matcher rules.

The AST visitor rule is based on the visitor pattern, and you just need to override certain methods (this abstract class provides the interface through which a list of nodes are accessed) to handle the validation logic within the corresponding node. Because OCLint uses an abstract syntax tree generated by Clang, knowing the Clang AST API is very helpful when writing rules.

The AST matcher rules are based on matching patterns, you need to construct some matchers and load them. Once a match is found, callback calls method with that AST node as an argument, and you can collect violation information in callback. (See here for more information on matchers)

That’s all we need to know, except that Oclint provides abstract classes for implementing custom rules. The details of the code will be covered in the next section.

What should I do next

So far, we’ve seen what OCLint offers and how to use it. We’ll take a closer look at how custom rules are written and debugged. Thus, with the help of built-in and custom rules, you should be able to cover all scenarios for static checking.

Click on the next chapter of OCLint getting started in action (part 2) : Custom Rules

Refer to the link

overview

manual

xcodebuild

rules