preface

In the process of developing the project more or less the use of static analysis tools to assist the completion of some similar grammar check, type analysis and so on. Mastering the necessary static analysis ability can improve the efficiency of project development and reduce unnecessary low-level errors.

Static analysis tools are commonly used

It is common to use the following static analysis tools during iOS development:

Analyzer:Clang Static Analyzer is a Static code scanning tool designed to analyze C, C++, and Objective-C programs. It has been integrated by Xcode, so it can be directly used for static code scanning analysis, or it can be used separately in the command line and provide output reports in HTML format and result files in XML format for easy integration into Jenkins for display

Infer: Is a static analysis tool developed by Facebook. Infer can analyze Objective-C, Java, or C code and report potential problems.

OCLint is a powerful static code analysis tool. It is based on Clang and can be used to improve code quality, find potential bugs, mainly for C, C ++ and Objective-C static analysis.

The above three commonly used static analysis tools have relatively complete function implementation, internal implementation is relatively complex, flexibility and self-defined extensible ability are not as convenient as their own implementation, clang can use C or C++ interface to complete static analysis, so the cost of learning and development is relatively high. Is there a lighter solution? The answer is yes: antLR based ultra-lightweight analysis tools. Next, this section demonstrates how to quickly build an ultra-lightweight, controllable, highly integrated static analysis tool by performing an analysis of Objective-C classes and printing out relevant information.

Build a lightweight static analysis tool

Antlr4 can quickly build a lightweight static analysis tool, choose their own appropriate language to quickly develop analysis business.

Install ANTLR4

Access antLR’s official website: www.antlr.org/, with the ‘macOS’ system… :

$$sudo CD/usr/local/lib curl -o https://www.antlr.org/download/antlr-4.9.2-complete.jar $export CLASSPATH=".:/usr/local/lib/antlr-4.9.2-complete.jar:$CLASSPATH" $alias antlr4=' java-jar Jar '$alias grun=' Java org.antlr.v4.4.gui.testrig'Copy the code

After the installation is complete, enter a value on the terminal

antlr4
Copy the code

Check whether the following information is displayed to check whether the installation is successfulAt presentantlrThe Runtime already supports the following languages

  • Java
  • C# (and an alternate C# target)
  • Python (2 and 3)
  • JavaScript
  • Go
  • C++
  • Swift
  • PHP
  • DART

You can develop static analysis tools in the language you are most familiar with or that currently suits you best. In this example, we will take the JavaScript language and develop a protocol based on Node.js to analyze all class implementations in the current Objective-C iOS project.

2. Install node.js development environment

Go to the Node.js website: nodejs.org/zh-cn/ and download a… :

node --version
Copy the code

Check whether the node.js version is correctly output.

3. Build static analysis tools

Create the Node.js analysis tool project

Input at terminal

npm init
Copy the code

Initialize oneNode.jsProject, generationindex.jsEntry file, add a startup script command, useVisual CodeThis is what it looks like when you open it, and this is what it looks like at the end:

npm run start
Copy the code

Check whether the system runs normally.

The installationJavaScripttheantlr4The runtime

npm install antlr4 --save
Copy the code

Generate JavsScript support parsing rules

antlrThis address provides almost all the language rules filesg4: Github.com/antlr/gramm…:

ObjectiveCLexer: Token parsing rule file ObjectiveCParser: Syntax (AST) Parsing rule file

The lexical rules file is first compiled using ANTLR

antlr4 -Dlanguage=JavaScript -no-listener ObjectiveCLexer.g4
Copy the code

Then compile the syntax rules file

antlr4 -Dlanguage=JavaScript -no-listener ObjectiveCParser.g4 
Copy the code

-no-listener: no code for the listener mode is generated. Antlr has two traversal modes: Visitor and Listener. Visitor is literally an access pattern, where the developer actively walks through the AST layer by layer, starting at the top of the AST. The Listener, on the other hand, is in listening mode, where the runtime iterates through the top layer of the AST and calls back the developer when a node is accessed. The xxxxvisitor.js that is automatically generated by the visitor pattern needs to refine the methods of some method nodes to check for rules in the syntax. The example in this section is to access the AST and obtain some key information on the node, which can be satisfied by using the method provided by Parser. Generate the following rule resolution file by compiling the above ANTLR command:

coding

Import the relevant JavsScript files and libraries in index.js:

import antlr4 from "antlr4";
import ObjectiveCLexer from "./ObjectiveCLexer.js";
import ObjectiveCParser from "./ObjectiveCParser.js";
import fs from "fs";

Copy the code

Because of the support hereES6theimportGrammar, sopackage.jsonNeed to state:

Have a test ready to useObjective-CThis section uses a very simple header file only to illustrate the use of instances:

Read objective-C files:

const input = fs.readFileSync("./FSBaseViewController.h", {
  encoding: "utf-8",
});
Copy the code

Parse the objective-C read into AST using the runtime syntax generated by ANTLR to parse the file

const chars = new antlr4.InputStream(input);
const lexer = new ObjectiveCLexer(chars);
const tokens = new antlr4.CommonTokenStream(lexer);
const parser = new ObjectiveCParser(tokens);
parser.buildParseTrees = true;
const tree = parser.translationUnit();
Copy the code

The ObjectiveCParser file is a rule parsing file generated in objectivecparser. g4, which can be obtained from objectivecParser. g4

The top node declared in the objectivecParser. g4 is translationUint.

From the declaration in objectivecParser. g4, translationUnit only declares two child nodes topLevelDeclaration* to indicate that the top-level node is one or more, and the EOF end node. This is because multiple Objective-C classes can be declared in the same source file. , the corresponding top-level node can be obtained by the following code. Since this section clearly has only one top-level vertex, the code is as follows:

const topLevelDeclarationNodes = tree.topLevelDeclaration(); if (topLevelDeclarationNodes.length == 0) return; const topLevelDeclarationNode = topLevelDeclarationNodes[0]; if (! topLevelDeclarationNode) return;Copy the code

or

const topLevelDeclarationNode = tree.topLevelDeclaration(0); if(! topLevelDeclarationNode) return;Copy the code

After obtaining topLevelDeclarationNode, look at the declarations in ObjectivecParser. g4 as follows:

This node declares a number of node types, but the classInterface node is the object of interest in this section. If you want to further determine whether the methods in the protocol are implemented, you can probe the clasImplementation node further.

const classInterfaceNode = topLevelDeclarationNode.classInterface(); if (! classInterfaceNode) return;Copy the code

The parsing rules for the classInterface node in objectivecParser. g4 are defined as follows:

Where classInterface contains className, maybe a protocolList which is an array, which is the Protocol that this class declaration implements.

Get the class name, objectivecParser. g4 to derive the node as a TerminalNode node containing a symbol, the string literal of the node.

/// GenericTypeSpecifierContext const classNameNode = classInterfaceNode.className; if (! classNameNode) return; const classNameIdentifierNode = classNameNode.identifier(); console.log(`class interface name: ${_getSymbolText(classNameIdentifierNode)}`);Copy the code

The _getSynbolText function is defined as follows:

function _getSymbolText(identifierNode) { if (! identifierNode) return null; if (! (identifierNode instanceof ObjectiveCParser.IdentifierContext)) return null; if (identifierNode && identifierNode.children && identifierNode.children instanceof Array && identifierNode.children.length > 0) { const terminalNodeImpl = identifierNode.children[0]; if (terminalNodeImpl) { const symbol = terminalNodeImpl.symbol; if (symbol) { return symbol.text; } } } return null; }Copy the code

Get the list of protocols implemented:

const protocolList = classInterfaceNode.protocolList();
if (protocolList && protocolList instanceof ObjectiveCParser.ProtocolListContext) {
  const protocolListNames = protocolList.children.map((protocol) => {
    const identifier = protocol.identifier();
    const protocolName = _getSymbolText(identifier);
    return {
      protocolName,
    };
  });
  console.log(protocolListNames);
}
Copy the code

The final running result is as follows:

Here is a quick and lightweight static analysis tool prototype based on ANTLR4. With a lot of practice, you can build a static analysis tool that can be quickly integrated into your project in 10 minutes. The integration is lightweight and controllable.

For more content, please pay attention to wechat public number << program ape moving bricks >>