前言
在開發(fā)項(xiàng)目的過程當(dāng)中或多或少的會(huì)利用靜態(tài)分析工具來輔助完成一些類似語(yǔ)法檢查、類型分析這樣的工作。掌握必要的靜態(tài)分析能力可以提升項(xiàng)目開發(fā)的效率,減少不必要的低級(jí)錯(cuò)誤。
常用靜態(tài)分析工具
在iOS
的開發(fā)過程中通常有以下的靜態(tài)分析工具可以使用:
Analyzer
:Clang Static Analyzer是一款靜態(tài)代碼掃描工具,專門用于針對(duì)C,C++和Objective-C的程序進(jìn)行分析。已經(jīng)被Xcode集成,可以直接使用Xcode進(jìn)行靜態(tài)代碼掃描分析,也可以單獨(dú)在命令行下使用并提供html格式的輸出報(bào)吿和xml格式的結(jié)果文件方便集成到Jenkins上進(jìn)行展示
Infer
:是Facebook開發(fā)的靜態(tài)分析工具。Infer 可以分析 Objective-C, Java 或者 C 代碼,報(bào)告潛在的問題。
OCLint
:是一個(gè)強(qiáng)大的靜態(tài)代碼分析工具,它基于clang
,可以用來提高代碼質(zhì)量,查找潛在的bug,主要針對(duì)c,c++和Objective-c的靜態(tài)分析,功能非常強(qiáng)大。
以上常用的三款靜態(tài)分析工具都有比較完整的功能實(shí)現(xiàn),內(nèi)部實(shí)現(xiàn)相對(duì)復(fù)雜,靈活性與自定義可擴(kuò)展能力都沒有自己實(shí)現(xiàn)一個(gè)方便,可以基于clang
利用C
或者C++
接口完成靜態(tài)分析,這樣實(shí)現(xiàn)的學(xué)習(xí)與開發(fā)成本也比較大。好有沒有輕量一點(diǎn)的解決方案呢,答案是肯定的: 基于antlr
的超輕量分析工具。接下來,本節(jié)將通過完成一個(gè)對(duì)Objective-C
的類進(jìn)行分析并打印出相關(guān)信息來說明怎么快速搭建一個(gè)超輕量、可控、高集成的靜態(tài)分析工具。
搭建輕量靜態(tài)分析工具
利用antlr4
可以快速搭建一個(gè)輕量的靜態(tài)分析工具,選擇自己合適的語(yǔ)言快速開發(fā)分析業(yè)務(wù)。
一、安裝antlr4
進(jìn)入到antlr
官網(wǎng): https://www.antlr.org/,以macOS
系統(tǒng)為例,輸入以下命令:
$ cd /usr/local/lib
$ sudo 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 /usr/local/lib/antlr-4.9.2-complete.jar'
$ alias grun='java org.antlr.v4.gui.TestRig'
安裝完成后,在終端輸入
antlr4
查看是否有以下內(nèi)容輸入,檢查是否安裝成功目前antlr
runtime已經(jīng)支持以下語(yǔ)言
你可以選擇一種你最熟悉或者說當(dāng)前最適合你的語(yǔ)言來開發(fā)靜態(tài)分析工具,本節(jié)實(shí)例將采集JavaScript
語(yǔ)言基于Node.js
開發(fā)一個(gè)用于分析當(dāng)前Objective-C
的iOS
項(xiàng)目的中所有類實(shí)現(xiàn)的協(xié)議。
二、安裝Node.js開發(fā)環(huán)境
進(jìn)入到Node.js
官網(wǎng): https://nodejs.org/zh-cn/,下載一個(gè)長(zhǎng)期支持版本或者當(dāng)前最新的版本都可以,安裝完成Node.js
后在終端輸入:
node --version
查看是否正確輸出Node.js
的版本。
三、搭建靜態(tài)分析工具
創(chuàng)建Node.js分析工具項(xiàng)目
在終端輸入
npm init
初始化一個(gè)Node.js
項(xiàng)目,生成index.js
入口文件,添加一個(gè)啟動(dòng)腳本命令,使用Visual Code
打開看上去是這樣的,最后它看上去是這樣的:
npm run start
查看是否能正常運(yùn)行。
安裝JavaScript
的antlr4
運(yùn)行時(shí)
npm install antlr4 --save
生成支持JavsScript解析規(guī)則
antlr
這個(gè)地址提供了幾乎所有的語(yǔ)言規(guī)則文件g4
: https://github.com/antlr/grammars-v4/tree/master/。這里下載objc
需要的規(guī)則文件,如下圖:
ObjectiveCLexer
:詞法(Token)解析規(guī)則文件ObjectiveCParser
:語(yǔ)法(AST)解析規(guī)則文件
首先利用antlr
編譯詞法規(guī)則文件
antlr4 -Dlanguage=JavaScript -no-listener ObjectiveCLexer.g4
然后再編譯語(yǔ)法規(guī)則文件
antlr4 -Dlanguage=JavaScript -no-listener ObjectiveCParser.g4
-no-listener
:表示不生成listener模式的相關(guān)代碼支持。
antlr
有兩種遍歷模式: visitor
與listener
。從字面的意思就可以看出visitor
是訪問模式,即開發(fā)者主動(dòng)從AST頂層開始一層一層的訪問遍歷AST。而listener
則為監(jiān)聽模式,即由運(yùn)行時(shí)從頂層AST開始層層遍歷訪問,當(dāng)訪問到一個(gè)節(jié)點(diǎn)時(shí)回調(diào)開發(fā)者。visitor
模式自動(dòng)生成的xxxxVisitor.js
需要完善一些方法節(jié)點(diǎn)的方法,以檢查語(yǔ)法中的規(guī)則。而本節(jié)實(shí)例是訪問AST并獲取節(jié)點(diǎn)上某些關(guān)鍵的信息,使用Parser
提供的方法即可滿足。
通過以上的antlr
命令編譯生成如下的規(guī)則解析文件:
編碼
在index.js
中導(dǎo)入相關(guān)的JavsScript
文件與庫(kù):
import antlr4 from "antlr4";
import ObjectiveCLexer from "./ObjectiveCLexer.js";
import ObjectiveCParser from "./ObjectiveCParser.js";
import fs from "fs";
由于這里支持ES6
的import
語(yǔ)法,所以package.json
中需要申明一下:
準(zhǔn)備好一個(gè)測(cè)試使用的Objective-C
的文件,本節(jié)使用的是一個(gè)非常簡(jiǎn)單的頭文件,僅用于說明實(shí)例的使用:
讀取Objective-C
文件:
const input = fs.readFileSync("./FSBaseViewController.h", {
encoding: "utf-8",
});
利用antlr
生成的運(yùn)行時(shí)語(yǔ)法解析文件,將讀取到的Objective-C
解析成AST
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();
這里的ObjectiveCParser
是根據(jù)ObjectiveCParser.g4
生成的規(guī)則解析文件,從ObjectiveCParser.g4
中可以到
ObjectiveCParser.g4
申明的頂層節(jié)點(diǎn)是translationUint。
從ObjectiveCParser.g4
中的申明可以看出, translationUnit
中只申明了兩個(gè)子節(jié)點(diǎn)topLevelDeclaration*
表示頂層節(jié)點(diǎn)是一個(gè)或者多個(gè),與EOF
結(jié)束節(jié)點(diǎn)。這是因?yàn)樵谕粋€(gè)源文件中可以申明多個(gè)Objective-C
的Class。,通過如下代碼即可取到對(duì)應(yīng)的頂層節(jié)點(diǎn),由于本節(jié)明確只有一個(gè)頂層頂點(diǎn),所以代碼如下:
const topLevelDeclarationNodes = tree.topLevelDeclaration();
if (topLevelDeclarationNodes.length == 0) return;
const topLevelDeclarationNode = topLevelDeclarationNodes[0];
if (!topLevelDeclarationNode) return;
或者
const topLevelDeclarationNode = tree.topLevelDeclaration(0);
if(!topLevelDeclarationNode) return;
獲取到topLevelDeclarationNode
之后,再查看ObjectiveCParser.g4
中的申明如下:
這個(gè)節(jié)點(diǎn)申明了很多種節(jié)點(diǎn)類型,在本節(jié)中關(guān)心的是classInterface
節(jié)點(diǎn)。如果你還想進(jìn)一步要判斷協(xié)議中的方法是否實(shí)現(xiàn),可以進(jìn)一步探查clasImplementation
節(jié)點(diǎn)。
const classInterfaceNode = topLevelDeclarationNode.classInterface();
if (!classInterfaceNode) return;
在ObjectiveCParser.g4
中classInterface
節(jié)點(diǎn)的解析規(guī)則定義如下:
其中classInterface
包含了className
,可能包含一個(gè)protocolList
它是一個(gè)數(shù)組,即這個(gè)類申明實(shí)現(xiàn)了的Protocol
。
獲取class name,ObjectiveCParser.g4
中可將節(jié)點(diǎn)推導(dǎo)成一個(gè)TerminalNode
節(jié)點(diǎn),節(jié)點(diǎn)包含一個(gè)symbol
即節(jié)點(diǎn)的字符串字面量。
/// GenericTypeSpecifierContext
const classNameNode = classInterfaceNode.className;
if (!classNameNode) return;
const classNameIdentifierNode = classNameNode.identifier();
console.log(`class interface name: ${_getSymbolText(classNameIdentifierNode)}`);
其中_getSynbolText
函數(shù)定義如下:
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;
}
獲取實(shí)現(xiàn)的協(xié)議列表:
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);
}
最終運(yùn)行結(jié)果如下:
到這里一個(gè)基于antlr4
的快速輕量靜態(tài)分析工具雛形就完成了,多嘗試練習(xí)一下即可在10分鈡搭建一個(gè)能快速集成到你的工程中的靜態(tài)分析工具,這個(gè)集成是輕量的、可控的。
-
C++
+關(guān)注
關(guān)注
21文章
2100瀏覽量
73453 -
代碼
+關(guān)注
關(guān)注
30文章
4722瀏覽量
68229 -
objective-c
+關(guān)注
關(guān)注
0文章
2瀏覽量
64
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論