本文主要是理解LLVM的編譯流程以及clang插件的開(kāi)發(fā)
LLVM
- LLVM是架構(gòu)編譯器的框架系統(tǒng),以C++編寫(xiě)而成淑翼,用于優(yōu)化任意程序語(yǔ)言編寫(xiě)的程序的編譯時(shí)間(compile-time)缤骨、鏈接時(shí)間(link-time)磁玉、運(yùn)行時(shí)間(run-time)以及空閑時(shí)間(idle-time)苛坚。對(duì)開(kāi)發(fā)者保持開(kāi)放妓羊,并兼容已有腳本
傳統(tǒng)編譯器設(shè)計(jì)
- 源碼 Source Code -> 前端 Frontend -> 優(yōu)化器 Optimizer -> 后端 Backend(代碼生成器 CodeGenerator)-> 機(jī)器碼 Machine Code胯究,如下圖所示
ios的編譯器架構(gòu)
- OC、C躁绸、C++使用的編譯器前端是Clang裕循,Swift是swift,后端都是LLVM净刮,如下圖所示
模塊說(shuō)明
前端 Frontend:編譯器前端的任務(wù)是解析源代碼(編譯階段)剥哑,它會(huì)進(jìn)行 詞法分析、語(yǔ)法分析淹父、語(yǔ)義分析株婴、檢查源代碼是否存在錯(cuò)誤,然后構(gòu)建抽象語(yǔ)法樹(shù)(Abstract Syntax Tree AST)暑认,LLVM的前端還會(huì)生成中間代碼(intermediate representation困介,簡(jiǎn)稱(chēng)IR),可以理解為llvm是編譯器 + 優(yōu)化器蘸际, 接收的是IR中間代碼座哩,輸出的還是IR,給后端粮彤,經(jīng)過(guò)后端翻譯成目標(biāo)指令集
優(yōu)化器 Optimizer:優(yōu)化器負(fù)責(zé)進(jìn)行各種優(yōu)化八回,改善代碼的運(yùn)行時(shí)間酷愧,例如消除冗余計(jì)算等
后端 Backend(代碼生成器 Code Generator):將代碼映射到目標(biāo)指令集,生成機(jī)器代碼缠诅,并且進(jìn)行機(jī)器代碼相關(guān)的代碼優(yōu)化
LLVM的設(shè)計(jì)
LLVM設(shè)計(jì)的最重要方面是溶浴,使用通用的代碼表示形式(IR),它是用來(lái)在編譯器中表示代碼的形式管引,所有LLVM可以為任何編程語(yǔ)言獨(dú)立編寫(xiě)前端士败,并且可以為任意硬件架構(gòu)獨(dú)立編寫(xiě)后端,如下所示
通俗的一句話(huà)理解就是:LLVM的設(shè)計(jì)是前后端分離
的褥伴,無(wú)論前端還是后端發(fā)生變化谅将,都不會(huì)影響另一個(gè)
Clang簡(jiǎn)介
clang是LLVM項(xiàng)目中的一個(gè)子項(xiàng)目,它是基于LLVM架構(gòu)圖的輕量級(jí)編譯器重慢,誕生之初是為了替代GCC饥臂,提供更快的編譯速度,它是負(fù)責(zé)C似踱、C++隅熙、OC語(yǔ)言的編譯器,屬于整個(gè)LLVM架構(gòu)中的 編譯器前端核芽,對(duì)于開(kāi)發(fā)者來(lái)說(shuō)囚戚,研究Clang可以給我們帶來(lái)很多好處
LLVM編譯流程
- 新建一個(gè)文件,寫(xiě)下如下代碼
int test(int a,int b){
return a + b + 3;
}
int main(int argc, const char * argv[]) {
int a = test(1, 2);
printf("%d",a);
return 0;
}
- 通過(guò)命令可以打印源碼的編譯流程
//************命令************
clang -ccc-print-phases main.m
//************編譯流程************
//0 - 輸入文件:找到源文件
+- 0: input, "main.m", objective-c
//1 - 預(yù)處理階段:這個(gè)過(guò)程處理包括宏的替換轧简,頭文件的導(dǎo)入
+- 1: preprocessor, {0}, objective-c-cpp-output
//2 - 編譯階段:進(jìn)行詞法分析驰坊、語(yǔ)法分析、檢測(cè)語(yǔ)法是否正確哮独,最終生成IR
+- 2: compiler, {1}, ir
//3 - 后端:這里L(fēng)LVM會(huì)通過(guò)一個(gè)一個(gè)的pass去優(yōu)化拳芙,每個(gè)pass做一些事情,最終生成匯編代碼
+- 3: backend, {2}, assembler
//4 - 匯編代碼生成目標(biāo)文件
+- 4: assembler, {3}, object
//5 - 鏈接:鏈接需要的動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)皮璧,生成可執(zhí)行文件
+- 5: linker, {4}, image(鏡像文件)
//6 - 綁定:通過(guò)不同的架構(gòu)态鳖,生成對(duì)應(yīng)的可執(zhí)行文件
6: bind-arch, "x86_64", {5}, image
下面分別針對(duì)上述流程來(lái)解釋?zhuān)渲?主要是輸入文件,即找到源文件恶导。這里不做過(guò)多說(shuō)明
一浆竭、預(yù)處理編譯階段
- 這個(gè)階段主要是處理包括宏的替換,頭文件的導(dǎo)入惨寿,可以執(zhí)行如下命令邦泄,執(zhí)行完畢可以看到
頭
文件的導(dǎo)入和宏
的替換
//在終端直接查看替換結(jié)果
clang -E main.m
//生成對(duì)應(yīng)的文件查看替換后的源碼
clang -E main.m >> main2.m
需要注意的是:
typedef 在給數(shù)據(jù)類(lèi)型取別名時(shí),在預(yù)處理階段不會(huì)被替換掉
define則在預(yù)處理階段會(huì)被替換裂垦,所以經(jīng)常被是用來(lái)進(jìn)行代碼混淆顺囊,目的是為了app安全,實(shí)現(xiàn)邏輯是:將app中核心類(lèi)蕉拢、核心方法等用系統(tǒng)相似的名稱(chēng)進(jìn)行取別名了害幅,然后在預(yù)處理階段就被替換了,來(lái)達(dá)到代碼混淆的目的
二杆逗、編譯階段
編譯階段主要是進(jìn)行詞法、語(yǔ)法等的分析和檢查站宗,然后生成中間代碼IR
1、詞法分析
預(yù)處理完成后就會(huì)進(jìn)行詞法分析益愈,這里會(huì)把代碼切成一個(gè)個(gè)token梢灭,比如大小括號(hào)、等于號(hào)還有字符串等
- 可以通過(guò)下面的命令查看
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
- 如果頭文件找不到蒸其,指定sdk
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -dump-tokens main.m
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -dump-tokens main.m
以下是代碼的詞法分析結(jié)果
2敏释、語(yǔ)法分析
詞法分析完成后就是語(yǔ)法分析,它的任務(wù)是驗(yàn)證語(yǔ)法是否正確摸袁,在詞法分析的基礎(chǔ)上將單詞序列組合成各類(lèi)此法短語(yǔ)钥顽,如程序、語(yǔ)句靠汁、表達(dá)式 等等蜂大,然后將所有節(jié)點(diǎn)組成抽象語(yǔ)法樹(shù)(Abstract Syntax Tree AST),語(yǔ)法分析程序判斷程序在結(jié)構(gòu)上是否正確
- 可以通過(guò)下面命令查看語(yǔ)法分析的結(jié)果
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
- 如果導(dǎo)入頭文件找不到膀曾,可以指定SDK
clang -isysroot (自己SDK路徑) -fmodules -fsyntax-only -Xclang -ast-dump main.m
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -fmodules -fsyntax-only -Xclang -ast-dump main.m
下面是語(yǔ)法分析的結(jié)果
其中,主要說(shuō)明幾個(gè)關(guān)鍵字的含義
-F unctionDecl 函數(shù)
- ParmVarDecl 參數(shù)
- CallExpr 調(diào)用一個(gè)函數(shù)
- BinaryOperator 運(yùn)算符
3阳啥、生成中間代碼IR
完成以上步驟后添谊,就開(kāi)始生成中間代碼IR了,代碼生成器(Code Generation)會(huì)將語(yǔ)法樹(shù)自頂向下遍歷逐步翻譯成LLVM IR察迟,
- 可以通過(guò)下面命令可以生成.ll的文本文件斩狱,查看IR代碼。OC代碼在這一步會(huì)進(jìn)行runtime橋接扎瓶,:property合成所踊、ARC處理等
clang -S -fobjc-arc -emit-llvm main.m
//以下是IR基本語(yǔ)法
@ 全局標(biāo)識(shí)
% 局部標(biāo)識(shí)
alloca 開(kāi)辟空間
align 內(nèi)存對(duì)齊
i32 32bit,4個(gè)字節(jié)
store 寫(xiě)入內(nèi)存
load 讀取數(shù)據(jù)
call 調(diào)用函數(shù)
ret 返回
下面是生成的中間代碼.ll文件
- IR文件在OC中是可以進(jìn)行優(yōu)化的概荷,一般設(shè)置是在target - Build Setting - Optimization Level(優(yōu)化器等級(jí))中設(shè)置秕岛。LLVM的優(yōu)化級(jí)別分別是-O0 -O1 -O2 -O3 -Os(第一個(gè)是大寫(xiě)英文字母O),下面是帶優(yōu)化的生成中間代碼IR的命令
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll
這是優(yōu)化后的中間代碼
- xcode7以后開(kāi)啟bitcode误证,蘋(píng)果會(huì)做進(jìn)一步優(yōu)化(
一般不使用
)继薛,生成.bc的中間代碼,我們通過(guò)優(yōu)化后的IR代碼生成.bc代碼
clang -emit-llvm -c main.ll -o main.bc
三愈捅、后端
LLVM在后端主要是會(huì)通過(guò)一個(gè)個(gè)的Pass去優(yōu)化遏考,每個(gè)Pass做一些事情,最終生成匯編代碼
生成匯編代碼
- 我們通過(guò)最終的.bc或者.ll代碼生成匯編代碼
clang -S -fobjc-arc main.ll -o main.s
或
clang -S -fobjc-arc main.bc -o main.s
- 生成匯編代碼也可以進(jìn)行優(yōu)化
clang -Os -S -fobjc-arc main.m -o main.s
四蓝谨、生成目標(biāo)文件
- 目標(biāo)文件的生成灌具,是
匯編器
以匯編代碼作為插入青团,將匯編
代碼轉(zhuǎn)
換為機(jī)器
代碼,最后輸出目標(biāo)文件(object file)
clang -fmodules -c main.s -o main.o
可以通過(guò)nm命令咖楣,查看下main.o中的符號(hào)
$xcrun nm -nm main.o
_printf函數(shù)是一個(gè)是undefined 督笆、external 的
undefined表示在當(dāng)前文件暫時(shí)找不到符號(hào)_printf
-
external表示這個(gè)符號(hào)是外部可以訪(fǎng)問(wèn)的
五、鏈接
鏈接主要是鏈接需要的動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)截歉,生成可執(zhí)行文件胖腾,其中
靜
態(tài)庫(kù)會(huì)和可
執(zhí)行文件合
并動(dòng)
態(tài)庫(kù)是獨(dú)
立的
連接器把編譯生成的.o文件和 .dyld .a文件鏈接,生成一個(gè)mach-o文件
clang main.o -o main
查看鏈接之后的符號(hào)
xcrun nm -nm main
結(jié)果如下所示瘪松,其中的undefined表示會(huì)在運(yùn)行時(shí)進(jìn)行動(dòng)態(tài)綁定
- 通過(guò)命令查看 main是什么格式咸作,此時(shí)是 mach-o可執(zhí)行文件
file main
// 打印結(jié)果:main:Mach-O 64-bit executable x86_64
./main
// 打印結(jié)果 :6%
六、綁定
綁定主要是通過(guò)不同的架構(gòu)宵睦,生成對(duì)應(yīng)的mach-o
格式可
執(zhí)行文件
總結(jié)
綜上记罚,所述,LLVM的編譯流程如下圖所示
Clang插件開(kāi)發(fā)
1壳嚎、準(zhǔn)備工作
由于國(guó)內(nèi)網(wǎng)絡(luò)限制桐智,需要借助鏡像下載llvm的源碼,此處為鏡像鏈接
- 下載LLVM項(xiàng)目
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
- 在LLVM的projects目錄下下載compiler-rt烟馅、libcxx说庭、libcxxabi
cd ../projects
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.git
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.git
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.git
- 在Clang的tools下安裝extra工具
cd ../tools/clang/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-extra.git
2、LLVM編譯
由于最新的LLVM只支持cmake來(lái)編譯郑趁,所以需要安裝cmake
安裝cmake
- 查看brew是否安裝cmake刊驴,如果已經(jīng)安裝,則跳過(guò)下面步驟
brew list
- 通過(guò)brew安裝cmake
brew install cmake
編譯LLVM
有兩種編譯方式:
- 通過(guò)
xcode
編譯LLVM - 通過(guò)
ninja
編譯LLVM
通過(guò)xcode編譯LLVM(方式一)
- cmake編譯成Xcode項(xiàng)目
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm
使用xcode編譯Clang
- 選擇自動(dòng)創(chuàng)建Schemes
- 編譯(CMD + B)寡润,選擇ALL_BUILD Secheme進(jìn)行編譯捆憎,預(yù)計(jì)1+小時(shí)
-
注
:這里通過(guò)ALL_BUILD
Secheme編譯會(huì)報(bào)以下錯(cuò)誤The i386 architecture is deprecated. You should update your ARCHS build setting to remove the i386 architecture,嘗試著去解決梭纹,但是目前尚未找到好的解決方案(后續(xù)會(huì)補(bǔ)充)
替代方案:選擇手動(dòng)創(chuàng)建Schemes躲惰,然后編譯編譯Clang + ClangTooling即可
通過(guò)ninja編譯LLVM(方式二)
- 使用ninja進(jìn)行編譯則還需要安裝ninja,使用以下命令安裝ninja
brew install ninja
在LLVM源碼根目錄下新建一個(gè)build_ninja目錄变抽,最終會(huì)在build_ninja目錄下生成``build.ninja`
在LLVM源碼根目錄下新建llvm_release目錄础拨,最終編譯文件會(huì)在llvm_release文件夾路徑下
cd llvm_build
//注意DCMAKE_INSTALL_PREFIX后面不能有空格
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX= 安裝路徑(本機(jī)為/ Users/xxx/xxx/LLVM/llvm_release)
- 一次執(zhí)行編譯,安裝指令
ninja
ninja install
3绍载、創(chuàng)建插件
-
在/llvm/tools/clang/tools下新建插件HKPlugin
在/llvm/tools/clang/tools目錄下的CMakeLists.txt文件太伊,新增add_clang_subdirectory(HKPlugin),此處的HKPlugin即為上一步創(chuàng)建的插件名稱(chēng)
在HKPlugin目錄下新建兩個(gè)文件逛钻,分別是HKPlugi.cpp 和CMakeLists.txt僚焦,并在CMakeLists.txt中加上以下代碼
//1、通過(guò)終端在HKPlugin目錄下的創(chuàng)建
touch HKPlugin.cpp
touch CMakeLists.txt
//2曙痘、CMakeLists.txt中添加以下代碼
add_llvm_library( HKPlugin MODULE BUILDTREE_ONLY
HKPlugin.cpp
)
- 接下來(lái)利用cmake重新生成Xcode項(xiàng)目芳悲,在build_xcode目錄下執(zhí)行以下命令
cmake -G Xcode ../llvm
- 最后可以在LLVM的xcode項(xiàng)目中可以看到Loadable modules目錄下由自定義的HKPlugin目錄了立肘,然后可以在里面編寫(xiě)插件代碼了
編寫(xiě)插件代碼
在HKPlugin目錄下的HKPlugin.cpp文件中,加入以下代碼
#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;
//命名空間名扛,和插件同名
namespace HKPlugin {
//第三步:掃描完畢的回調(diào)函數(shù)
//4谅年、自定義回調(diào)類(lèi),繼承自MatchCallback
class HKMatchCallback: public MatchFinder::MatchCallback {
private:
//CI傳遞路徑:HKASTAction類(lèi)中的CreateASTConsumer方法參數(shù) - HKConsumer的構(gòu)造函數(shù) - HKMatchCallback的私有屬性肮韧,通過(guò)構(gòu)造函數(shù)從CJLASTConsumer構(gòu)造函數(shù)中獲取
CompilerInstance &CI;
//判斷是否是用戶(hù)源文件
bool isUserSourceCode(const string filename) {
//文件名不為空
if (filename.empty()) return false;
//非xcode中的源碼都認(rèn)為是用戶(hù)的
if (filename.find("/Applications/Xcode.app/") == 0) return false;
return true;
}
//判斷是否應(yīng)該用copy修飾
bool isShouldUseCopy(const string typeStr) {
//判斷類(lèi)型是否是NSString | NSArray | NSDictionary
if (typeStr.find("NSString") != string::npos ||
typeStr.find("NSArray") != string::npos ||
typeStr.find("NSDictionary") != string::npos/*...*/)
{
return true;
}
return false;
}
public:
HKMatchCallback(CompilerInstance &CI) :CI(CI) {}
//重寫(xiě)run方法
void run(const MatchFinder::MatchResult &Result) {
//通過(guò)result獲取到相關(guān)節(jié)點(diǎn) -- 根據(jù)節(jié)點(diǎn)標(biāo)記獲热邗濉(標(biāo)記需要與CJLASTConsumer構(gòu)造方法中一致)
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
//判斷節(jié)點(diǎn)有值,并且是用戶(hù)文件
if (propertyDecl && isUserSourceCode(CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str()) ) {
//15弄企、獲取節(jié)點(diǎn)的描述信息
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
//獲取節(jié)點(diǎn)的類(lèi)型超燃,并轉(zhuǎn)成字符串
string typeStr = propertyDecl->getType().getAsString();
// cout<<"---------拿到了:"<<typeStr<<"---------"<<endl;
//判斷應(yīng)該使用copy,但是沒(méi)有使用copy
if (propertyDecl->getTypeSourceInfo() && isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
//使用CI發(fā)警告信息
//通過(guò)CI獲取診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//通過(guò)診斷引擎 report報(bào)告 錯(cuò)誤拘领,即拋出異常
/*
錯(cuò)誤位置:getBeginLoc 節(jié)點(diǎn)開(kāi)始位置
錯(cuò)誤:getCustomDiagID(等級(jí)意乓,提示)
*/
diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0 - 這個(gè)地方推薦使用copy!!"))<< typeStr;
}
}
}
};
//第二步:掃描配置完畢
//3、自定義HKASTConsumer约素,繼承自ASTConsumer届良,用于監(jiān)聽(tīng)AST節(jié)點(diǎn)的信息 -- 過(guò)濾器
class HKASTConsumer: public ASTConsumer {
private:
//AST節(jié)點(diǎn)的查找過(guò)濾器
MatchFinder matcher;
//定義回調(diào)類(lèi)對(duì)象
HKMatchCallback callback;
public:
//構(gòu)造方法中創(chuàng)建matcherFinder對(duì)象
HKASTConsumer(CompilerInstance &CI) : callback(CI) {
//添加一個(gè)MatchFinder,每個(gè)objcPropertyDecl節(jié)點(diǎn)綁定一個(gè)objcPropertyDecl標(biāo)識(shí)(去匹配objcPropertyDecl節(jié)點(diǎn))
//回調(diào)callback圣猎,其實(shí)是在HKMatchCallback里面重寫(xiě)run方法(真正回調(diào)的是回調(diào)run方法)
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
//實(shí)現(xiàn)兩個(gè)回調(diào)方法 HandleTopLevelDecl 和 HandleTranslationUnit
//解析完一個(gè)頂級(jí)的聲明士葫,就回調(diào)一次(頂級(jí)節(jié)點(diǎn),相當(dāng)于一個(gè)全局變量送悔、函數(shù)聲明)
bool HandleTopLevelDecl(DeclGroupRef D){
// cout<<"正在解析..."<<endl;
return true;
}
//整個(gè)文件都解析完成的回調(diào)
void HandleTranslationUnit(ASTContext &context) {
// cout<<"文件解析完畢!"<<endl;
//將文件解析完畢后的上下文context(即AST語(yǔ)法樹(shù)) 給 matcher
matcher.matchAST(context);
}
};
//2慢显、繼承PluginASTAction,實(shí)現(xiàn)我們自定義的Action放祟,即自定義AST語(yǔ)法樹(shù)行為
class HKASTAction: public PluginASTAction {
public:
//重載ParseArgs 和 CreateASTConsumer方法
bool ParseArgs(const CompilerInstance &ci, const std::vector<std::string> &args) {
return true;
}
//返回ASTConsumer類(lèi)型對(duì)象鳍怨,其中ASTConsumer是一個(gè)抽象類(lèi)呻右,即基類(lèi)
/*
解析給定的插件命令行參數(shù)跪妥。
- param CI 編譯器實(shí)例,用于報(bào)告診斷声滥。
- return 如果解析成功眉撵,則為true;否則落塑,插件將被銷(xiāo)毀纽疟,并且不執(zhí)行任何操作。該插件負(fù)責(zé)使用CompilerInstance的Diagnostic對(duì)象報(bào)告錯(cuò)誤憾赁。
*/
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
//返回自定義的CJLASTConsumer,即ASTConsumer的子類(lèi)對(duì)象
/*
CI用于:
- 判斷文件是否使用戶(hù)的
- 拋出警告
*/
return unique_ptr<HKASTConsumer> (new HKASTConsumer(CI));
}
};
}
//第一步:注冊(cè)插件污朽,并自定義AST語(yǔ)法樹(shù)Action類(lèi)
//1、注冊(cè)插件
static FrontendPluginRegistry::Add<HKPlugin::HKASTAction> HK("HKPlugin", "This is HKPlugin");
其原理主要分為三步
-
【第一步】注冊(cè)插件龙考,并自定義AST語(yǔ)法樹(shù)Action類(lèi)
繼承
自PluginASTAction蟆肆,自定義ASTAction
矾睦,需要重載兩
個(gè)方法ParseArgs
和CreateASTConsumer
,其中的重點(diǎn)方法是CreateASTConsumer
炎功,方法中有個(gè)參數(shù)CI
即編譯實(shí)例對(duì)象
枚冗,主要用于以下兩個(gè)方面用于判斷文件是否是用戶(hù)的
用于拋出警告
通過(guò)FrontendPluginRegistry注冊(cè)插件,需要關(guān)聯(lián)插件名與自定義的
ASTAction
類(lèi)
-
【第二步】掃描配置完畢
繼承
自ASTConsumer
類(lèi)蛇损,實(shí)現(xiàn)自定義的子類(lèi)HKASTConsumer
,有兩個(gè)參
數(shù)MatchFinder
對(duì)象matcher
以及HKMatchCallback
自定義的回調(diào)對(duì)象callback實(shí)現(xiàn)構(gòu)造函數(shù)赁温,主要是創(chuàng)建
MatchFinder
對(duì)象,以及將CI
傳給回調(diào)對(duì)象-
實(shí)現(xiàn)兩個(gè)回調(diào)方法
-
HandleTopLevelDecl
:解析完一
個(gè)頂級(jí)的聲明淤齐,就回調(diào)一次 -
HandleTranslationUnit
:整
個(gè)文件都解析完成的回調(diào)股囊,將文件解析完畢后的上下文context(即AST語(yǔ)法樹(shù)) 給 matcher
-
-
【第三步】掃描完畢的回調(diào)函數(shù)
- 繼承自MatchFinder::MatchCallback,自定義回調(diào)類(lèi)HKMatchCallback
- 定義CompilerInstance私有屬性床玻,用于接收ASTConsumer類(lèi)傳遞過(guò)來(lái)的CI信息
- 重寫(xiě)run方法
- 1毁涉、通過(guò)result,根據(jù)節(jié)點(diǎn)標(biāo)記锈死,獲取相應(yīng)節(jié)點(diǎn)贫堰,此時(shí)的標(biāo)記需要與HKASTConsumer構(gòu)造方法中一致
- 2、判斷節(jié)點(diǎn)有值待牵,并且是用戶(hù)文件即isUserSourceCode私有方法
- 3其屏、獲取節(jié)點(diǎn)的描述信息
- 4、獲取節(jié)點(diǎn)的類(lèi)型缨该,并轉(zhuǎn)成字符串
- 5偎行、判斷應(yīng)該使用copy,但是沒(méi)有使用copy
- 6贰拿、通過(guò)CI獲取診斷引擎
-
7蛤袒、通過(guò)診斷引擎報(bào)告錯(cuò)誤
所以,綜上所述膨更,clang插件開(kāi)發(fā)的流程圖如下
clang插件開(kāi)發(fā)細(xì)節(jié)圖
然后在終端中測(cè)試插件
//命令格式
自己編譯的clang文件路徑 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang 插件(.dyld)路徑 -Xclang -add-plugin -Xclang 插件名 -c 源碼路徑
//例子
/Users/XXX/Desktop/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk/ -Xclang -load -Xclang /Users/XXXX/Desktop/build_xcode/Debug/lib/CJLPlugin.dylib -Xclang -add-plugin -Xclang CJLPlugin -c /Users/XXXX/Desktop/XXX/XXXX/測(cè)試demo/testClang/testClang/ViewController.m
4妙真、Xcode集成插件
加載插件
- 打開(kāi)測(cè)試項(xiàng)目,在target->Build Settings -> Other C Flags 添加以下內(nèi)容
-Xclang -load -Xclang (.dylib)動(dòng)態(tài)庫(kù)路徑 -Xclang -add-plugin -Xclang HKPlugin
設(shè)置編譯器
- 由于clang插件需要使用對(duì)應(yīng)的版本去加載荚守,如果版本不一致會(huì)導(dǎo)致編譯失敗珍德,如下所示
- 在Build Settings欄目中新增兩項(xiàng)用戶(hù)定義的設(shè)置,分別是CC和CXX
- CC 對(duì)應(yīng)的是自己編譯的clang的絕對(duì)路徑
- CXX 對(duì)應(yīng)的是自己編譯的clang++的絕對(duì)路徑
- 接下來(lái)在
Build Settings
中搜
索index
矗漾,將Enable Index-Wihle-Building Functionality的Default改
為NO
-
最后锈候,重新編譯測(cè)試項(xiàng)目,會(huì)出現(xiàn)下面的效果