-
上一節(jié),我們熟悉了
LLVM
的完整流程
。
本節(jié):
- 自定義
Clang插件
是實現(xiàn)Copy屬性修飾符
的檢測
和智能提示
。
(最終的使用場景
可能并沒那么通用
嚎幸,但主要目的
是徹底熟悉LLVM
)- 文末會附上
最終代碼
和使用方法
。
- 配置LLVM環(huán)境
- 自定義插件
- 如果你也跟我一樣
充滿
著期待
察滑。那我們就開始
吧。
1. 配置LLVM環(huán)境
?? ?? ?? 【注意】
LLVM
源碼2.29G
,編譯后
文件將近30G
纳猫,請確保
電腦硬盤空間足夠
;編譯時
竹捉,電腦溫度會飆升90多度
芜辕,CPU
資源占滿
,請用空調(diào)伺候
著块差,可能
會黑屏
侵续;編譯時間
長達(dá)1個多小時
倔丈,請合理安排時間。
如果以上3點
状蜗,你確定能接受
需五,那我們就開始
吧。
1.1 LLVM下載
- 在github(?? 下載地址)下載
LLVM
相關(guān)資源庫
:
clang
轧坎、clang-tools-extra
宏邮、compiler-rt
、libcxx
眶根、libcxxabi
蜀铲、llvm
五個庫:(我下載的都是11.0.0版本的)
image.png
-
解壓
并移除
名稱中的版本號
image.png
- 按以下順序?qū)?code>文件夾移到
指定位置
:
- 將
clang-tools-extra
移到clang
文件夾中的clang/tools
文件中 - 將
clang
文件夾移到llvm/tools
中 - 將
compiler-rt
边琉、libcxx
属百、libcxxabi
都移到llvm/projects
中
image.png
1.2 安裝cmake
- 查看
brew列表
,檢查是否安裝過cmake
变姨,如果有族扰,就跳過此步驟
brew list
- 如果沒有,就使用
brew安裝
:
brew install cmake
- 如果報權(quán)限錯誤定欧,可
sudo chown -R `whoami`:admin /usr/local/share
放開權(quán)限
image.png
1.3 編譯
- 在
llvm
同級目錄創(chuàng)建build
文件夾渔呵,cd
到build文件夾,運行cmake
命令砍鸠,將llvm
編譯成Xcode
項目
cd build
cmake -G Xcode ../llvm
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="Release" ../llvm
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="debug" ../llvm
注意:
build
文件夾是存放cmake
生成的Xcode文件
的扩氢。放哪里都可以。
cmake
編譯的對象是llvm
文件爷辱。所以使用cmake -G Xcode ../llvm
編譯并生成Xcode文件時录豺,請核對llvm
的文件路徑
。
-
成功之后饭弓,可以看到生成的
Xcode
文件:
image.png 打開
LLVM.xcodeproj
選擇
手動創(chuàng)建Schemes
image.png
- 添加
clang
和clangTooling
兩個Target双饥,并完成
兩個target的編譯
(此處可能需要1小時
,cpu占滿
弟断,請適當(dāng)給電腦降溫
??)
image.png
-
編譯成功
后咏花,我們的準(zhǔn)備工作
就完成
了》浚可以正式開始插件開發(fā)
了
2. 自定義插件
2.1 添加插件
- 我們在
llvm/tools/clang/tools
文件夾中昏翰,創(chuàng)建HTPlugin
文件夾,并新增
兩個文件:
image.png
CMakeLists.txt
內(nèi)容:add_llvm_library( HTPlugin MODULE BUILDTREE_ONLY HTPlugin.cpp)
HTPlugin.cpp
:空文件
image.png
- 在
llvm/tools/clang/tools
文件夾的CMakeLists.txt
文件尾部刘急,加上add_clang_subdirectory(HTPlugin)
- 創(chuàng)建好后棚菊,我們回到
build
文件夾,cmake -G Xcode ../llvm
重新編譯
生成Xcode
文件排霉。
開始編譯
編譯完成
ps: 我的
電腦系統(tǒng)版本
是:
image.png
- 打開
build文件夾
中新生成的LLVM.xcodeproj
:
Command + 鼠標(biāo)左鍵
窍株,點擊
文件夾民轴,折疊所有文件夾。 在Loadable modules
中球订,可以看到我們自己創(chuàng)建的HTPlugin
文件夾:
image.png搜索
并選擇 HTPlugin
:
image.png
2.2 書寫插件
2.2.1 體驗單文件
的頂級節(jié)點
解析
1. 在HTPlugn.cpp
文件中加入
以下代碼
:
// 1. 聲明命名空間后裸,創(chuàng)建插件
namespace HTPlugin {
// 4. 自定義HTConsumer,繼承ASTConsumer
// 進(jìn)入`ASTConsumer`冒滩,查看它的結(jié)構(gòu)微驶,有很多選擇
// 本例重載[HandleTopLevelDecl]頂級節(jié)點的解析和[HandleTranslationUnit]文件解析結(jié)束回調(diào)
class HTConsumer: public ASTConsumer {
private:
public:
// 4.1 頂級節(jié)點解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"解析中..."<<endl;
return true;
}
// 4.2 單個文件解析結(jié)束
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完畢!"<<endl;
}
};
// 2. 創(chuàng)建插件(繼承PluginASTAction 實現(xiàn)自定義的ASTAction)
// 【目的】讀取AST語法樹的所有節(jié)點开睡。
// 【如何重寫】 進(jìn)入`PluginASTAction`因苹,查看它的結(jié)構(gòu),重載[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.1 解析成功篇恒,就返回true扶檐。 我們直接寫true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.2 創(chuàng)建一個語法樹對象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定義的HTConsumer,繼承自ASTConsumer胁艰。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
return unique_ptr<HTConsumer>(new HTConsumer());
}
};
}
// 3. 注冊插件 (參數(shù)1:插件名稱款筑, 參數(shù)2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
2. Command+B
編譯一次
3. 創(chuàng)建一個測試文件demo.m
:
-
demo.m
內(nèi)容:
int sum(int a);
int a = 10;
int b = 20;
int sum(int a) {
int b = 10;
return 10 + b;
}
int sum2(int a, int b) {
int c = 10;
return a + b + c;
}
4. cd
到ClangDemo
文件夾,使用我們的clang
編譯demo.m
文件:
/Users/ht/Desktop/llvm/build/Debug/bin/clang -usysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk/ -Xclang -load -Xclang /Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib -Xclang -add-plugin -Xclang HTPlugin -c demo.m
格式分析:
/Users/ht/Desktop/llvm/build/Debug/bin/clang
:
自己插件編譯后
的clang
文件腾么,在build/Debug/bin/
文件夾中:
image.png/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk/
:
使用自己本機(jī)
的SDK
絕對路徑奈梳。 (我這是14.2)
image.png/Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib
:
自己插件編譯后
的HTPlugin.dylib
插件動態(tài)庫絕對路徑
,在build/Debug/lib/
文件夾中:
image.png- 指定使用
HTPlugin
編譯demo.m
文件
5. 編譯完成后解虱,會生成demo.o
文件攘须。是一個object目標(biāo)文件
。(不懂目標(biāo)文件殴泰,可回顧上一節(jié))
2.2.2 觀察屬性修飾符
節(jié)點名
1. 創(chuàng)建工程
-
在
ClangDemo
文件夾中于宙,創(chuàng)建demo
工程:
image.png 在
ViewController.m
文件中加入測試代碼
:
@interface ViewController()
// NSString、NSArray艰匙、NSDictionary 應(yīng)使用Copy修飾
@property (nonatomic, strong) NSString * name;
@property (nonatomic, strong) NSArray * arrs;
@property (nonatomic, strong) NSDictionary *dicts;
@end
2. 使用系統(tǒng)clang
編譯ViewController.m
文件
-
cd
到ViewController.m
的文件夾限煞,使用系統(tǒng)clang
編譯ViewController.m
文件:
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m
- 可以發(fā)現(xiàn)在
AST語法樹
中,ObjCPropertyDecl
是屬性節(jié)點
员凝。
2.2.3 過濾ObjCPropertyDecl
節(jié)點
- 修改
HTPlugin
文件:
#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;
// 1. 聲明命名空間署驻,創(chuàng)建插件
namespace HTPlugin {
// 5. 自定義HTMatchCallBack(過濾器回調(diào)類) 繼承自MatchFinder的MatchCallBack
class HTMatchCallBack: public MatchFinder::MatchCallback {
public:
// 5.2 重寫run方法
void run(const MatchFinder::MatchResult &Result) {
// 通過Result拿到了所有節(jié)點, 通過自定義的節(jié)點標(biāo)識,拿到我們標(biāo)記的所有節(jié)點
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
// 過濾空情況
if (propertyDecl) {
// 拿到類型健霹,轉(zhuǎn)string旺上,打印
string typeStr = propertyDecl->getType().getAsString();
cout<<"--- 拿到了: 【"<<typeStr<<"】 ----"<<endl;
}
}
};
// 4. 自定義HTConsumer,繼承ASTConsumer
// 進(jìn)入`ASTConsumer`糖埋,查看它的結(jié)構(gòu)宣吱,有很多選擇
// 本例重載[HandleTopLevelDecl]頂級節(jié)點的解析和[HandleTranslationUnit]文件解析結(jié)束回調(diào)
class HTConsumer: public ASTConsumer {
private:
// 4.3 添加屬性:AST節(jié)點查找過濾器
MatchFinder matcher;
// 5.1 添加屬性:MatchFinder過濾器的回調(diào)函數(shù)
HTMatchCallBack callback;
public:
// 4.5 聲明構(gòu)造方法(出廠就添加一個MatchFinder過濾器)
HTConsumer() {
// 添加MatchFinder (參數(shù)1: 過濾的節(jié)點, 參數(shù)2瞳别, 過濾后的回調(diào))
// 查看MatchFinder中的MatchCallBack的結(jié)構(gòu)征候,仿寫一個回調(diào)函數(shù)
// 此處杭攻,過濾`objcPropertyDecl`屬性節(jié)點,手動添加節(jié)點標(biāo)識`objcPropertyDecl`
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
// 4.1 頂級節(jié)點解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"解析中..."<<endl;
return true;
}
// 4.2 單個文件解析結(jié)束
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完畢疤坝!"<<endl;
// 4.4 解析完成后兆解,將語法樹給到`matcher`過濾器
matcher.matchAST(Ctx);
}
};
// 2. 創(chuàng)建插件(繼承PluginASTAction 實現(xiàn)自定義的ASTAction)
// 【目的】讀取AST語法樹的所有節(jié)點。
// 【如何重寫】 進(jìn)入`PluginASTAction`跑揉,查看它的結(jié)構(gòu)锅睛,重載[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.2 解析成功,就返回true历谍。 我們直接寫true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.3 創(chuàng)建一個語法樹對象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定義的HTConsumer现拒,繼承自ASTConsumer。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
return unique_ptr<HTConsumer>(new HTConsumer());
}
};
}
// 3. 注冊插件 (參數(shù)1:插件名稱望侈, 參數(shù)2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
-
Command+B
編譯 -
cd
到llvm/ClangDemo/Demo/Demo
文件夾印蔬。 使用我們自己的clang
和HTPlugin
插件,編譯ViewController.m
文件:
/Users/ht/Desktop/llvm/build/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk -Xclang -load -Xclang /Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib -Xclang -add-plugin -Xclang HTPlugin -c ViewController.m
分享我掉的一個坑:
- 這是我自己
作
甜无,因為我之前之前寫了MacOS
項目扛点,所以創(chuàng)建Demo
時哥遮,默認(rèn)創(chuàng)建了MacOS
工程岂丘,然后我使用了iPhoneSimulator14.2.sdk
的iOS SDK
來編譯。一直報'Cocoa/Cocoa.h' file not found
image.png出現(xiàn)
Cocoa/Cocoa.h
的問題眠饮,都是MacOS
的問題奥帘,iOS使用Fundation
庫。macOS使用Cocoa
庫仪召。
-
可以看到
打印
了非常多的屬性節(jié)點
信息:
image.png 其中
大部分
都是系統(tǒng)文件
的屬性
寨蹋,我們需要排除
這些,定位
到自己代碼
的屬性
扔茅。
2.2.4 過濾系統(tǒng)文件
節(jié)點
可以通過編譯器實例
(文件)CompilerInstance已旧,讀取每個實例
的路徑
。代碼如下:(新增6)
// 1. 聲明命名空間召娜,創(chuàng)建插件
namespace HTPlugin {
// 5. 自定義HTMatchCallBack(過濾器回調(diào)類) 繼承自MatchFinder的MatchCallBack
class HTMatchCallBack: public MatchFinder::MatchCallback {
private:
// 6.2 添加屬性:編譯器實例
CompilerInstance &compilerInstance;
public:
// 6.3 聲明構(gòu)造方法运褪,入?yún)⑿略鯟ompilerInstance編譯器實例,并賦值給compilerInstance
HTMatchCallBack(CompilerInstance &CI):compilerInstance(CI) { }
// 5.2 重寫run方法 (回調(diào)的執(zhí)行函數(shù))
void run(const MatchFinder::MatchResult &Result) {
// 通過Result拿到了所有節(jié)點, 通過自定義的節(jié)點標(biāo)識,拿到我們標(biāo)記的所有節(jié)點
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
// 過濾空情況
if (propertyDecl) {
// 6.4 打印文件名稱
string fileName = compilerInstance.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
// 拿到類型玖瘸,轉(zhuǎn)string秸讹,打印
string typeStr = propertyDecl->getType().getAsString();
cout<<"--- 拿到了: 【"<<typeStr<<"】 ----文件路徑:"<<fileName<<endl;
}
}
};
// 4. 自定義HTConsumer,繼承ASTConsumer
// 進(jìn)入`ASTConsumer`雅倒,查看它的結(jié)構(gòu)璃诀,有很多選擇
// 本例重載[HandleTopLevelDecl]頂級節(jié)點的解析和[HandleTranslationUnit]文件解析結(jié)束回調(diào)
class HTConsumer: public ASTConsumer {
private:
// 4.3 添加屬性:AST節(jié)點查找過濾器
MatchFinder matcher;
// 5.1 添加屬性:MatchFinder過濾器的回調(diào)函數(shù)
HTMatchCallBack callback;
public:
// 4.5 聲明構(gòu)造方法(出廠就添加一個MatchFinder過濾器)
// 6.4 入?yún)⑿略觯壕幾g器實例CI,并賦值給callback
HTConsumer(CompilerInstance &CI):callback(CI) {
// 添加MatchFinder (參數(shù)1: 過濾的節(jié)點, 參數(shù)2蔑匣, 過濾后的回調(diào))
// 查看MatchFinder中的MatchCallBack的結(jié)構(gòu)劣欢,仿寫一個回調(diào)函數(shù)
// 此處棕诵,過濾`objcPropertyDecl`屬性節(jié)點,手動添加節(jié)點標(biāo)識`objcPropertyDecl`
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
// 4.1 頂級節(jié)點解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"解析中..."<<endl;
return true;
}
// 4.2 單個文件解析結(jié)束
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完畢凿将!"<<endl;
// 4.4 解析完成后年鸳,將語法樹給到`matcher`過濾器
matcher.matchAST(Ctx);
}
};
// 2. 創(chuàng)建插件(繼承PluginASTAction 實現(xiàn)自定義的ASTAction)
// 【目的】讀取AST語法樹的所有節(jié)點。
// 【如何重寫】 進(jìn)入`PluginASTAction`丸相,查看它的結(jié)構(gòu)搔确,重載[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.2 解析成功,就返回true灭忠。 我們直接寫true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.3 創(chuàng)建一個語法樹對象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定義的HTConsumer膳算,繼承自ASTConsumer。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
// 6.1 存儲CompilerInstance實例(它是每個編譯文件弛作,可以通過它讀取編譯文件的路徑涕蜂,剔除系統(tǒng)文件的干擾)
return unique_ptr<HTConsumer>(new HTConsumer(CI));
}
};
}
// 3. 注冊插件 (參數(shù)1:插件名稱, 參數(shù)2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
-
Command+B
編譯后映琳,使用自己的clang
和HTPlugin
編譯ViewController.m
文件:
/Users/ht/Desktop/llvm/build/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk -Xclang -load -Xclang /Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib -Xclang -add-plugin -Xclang HTPlugin -c ViewController.m
-
可以發(fā)現(xiàn)机隙,所有系統(tǒng)文件,地址都是以
/Applications/Xcode.app/
開頭萨西。
image.png 通過區(qū)分
文件地址
有鹿,可以剔除
所有系統(tǒng)文件
:(新增7)
#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;
// 1. 聲明命名空間,創(chuàng)建插件
namespace HTPlugin {
// 5. 自定義HTMatchCallBack(過濾器回調(diào)類) 繼承自MatchFinder的MatchCallBack
class HTMatchCallBack: public MatchFinder::MatchCallback {
private:
// 6.2 添加屬性:編譯器實例
CompilerInstance &compilerInstance;
// 7. 判斷是否是用戶代碼的函數(shù)(通過文件地址判斷)
bool isUserSourceCode(const string fileName) {
// 空谎脯,不是用戶的
if (fileName.empty()) return false;
// Xcode中的源碼葱跋,都是系統(tǒng)的
if (fileName.find("/Applications/Xcode.app/") == 0) return false;
// 其他情況,都是用戶的
return true;
}
public:
// 6.3 聲明構(gòu)造方法源梭,入?yún)⑿略鯟ompilerInstance編譯器實例,并賦值給compilerInstance
HTMatchCallBack(CompilerInstance &CI):compilerInstance(CI) { }
// 5.2 重寫run方法 (回調(diào)的執(zhí)行函數(shù))
void run(const MatchFinder::MatchResult &Result) {
// 通過Result拿到了所有節(jié)點, 通過自定義的節(jié)點標(biāo)識娱俺,拿到我們標(biāo)記的所有節(jié)點
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
// 過濾空情況
if (propertyDecl) {
// 6.4 打印文件名稱
string fileName = compilerInstance.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
// 7.1 不是用戶的文件,就不往下打印
if (!isUserSourceCode(fileName)) return;
// 拿到類型废麻,轉(zhuǎn)string荠卷,打印
string typeStr = propertyDecl->getType().getAsString();
cout<<"--- 拿到了: 【"<<typeStr<<"】 ----文件路徑:"<<fileName<<endl;
}
}
};
// 4. 自定義HTConsumer,繼承ASTConsumer
// 進(jìn)入`ASTConsumer`烛愧,查看它的結(jié)構(gòu)油宜,有很多選擇
// 本例重載[HandleTopLevelDecl]頂級節(jié)點的解析和[HandleTranslationUnit]文件解析結(jié)束回調(diào)
class HTConsumer: public ASTConsumer {
private:
// 4.3 添加屬性:AST節(jié)點查找過濾器
MatchFinder matcher;
// 5.1 添加屬性:MatchFinder過濾器的回調(diào)函數(shù)
HTMatchCallBack callback;
public:
// 4.5 聲明構(gòu)造方法(出廠就添加一個MatchFinder過濾器)
// 6.4 入?yún)⑿略觯壕幾g器實例CI,并賦值給callback
HTConsumer(CompilerInstance &CI):callback(CI) {
// 添加MatchFinder (參數(shù)1: 過濾的節(jié)點, 參數(shù)2屑彻, 過濾后的回調(diào))
// 查看MatchFinder中的MatchCallBack的結(jié)構(gòu)验庙,仿寫一個回調(diào)函數(shù)
// 此處,過濾`objcPropertyDecl`屬性節(jié)點社牲,手動添加節(jié)點標(biāo)識`objcPropertyDecl`
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
// 4.1 頂級節(jié)點解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"解析中..."<<endl;
return true;
}
// 4.2 單個文件解析結(jié)束
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完畢粪薛!"<<endl;
// 4.4 解析完成后,將語法樹給到`matcher`過濾器
matcher.matchAST(Ctx);
}
};
// 2. 創(chuàng)建插件(繼承PluginASTAction 實現(xiàn)自定義的ASTAction)
// 【目的】讀取AST語法樹的所有節(jié)點搏恤。
// 【如何重寫】 進(jìn)入`PluginASTAction`违寿,查看它的結(jié)構(gòu)湃交,重載[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.2 解析成功,就返回true藤巢。 我們直接寫true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.3 創(chuàng)建一個語法樹對象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定義的HTConsumer搞莺,繼承自ASTConsumer。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
// 6.1 存儲CompilerInstance實例(它是每個編譯文件掂咒,可以通過它讀取編譯文件的路徑才沧,剔除系統(tǒng)文件的干擾)
return unique_ptr<HTConsumer>(new HTConsumer(CI));
}
};
}
// 3. 注冊插件 (參數(shù)1:插件名稱, 參數(shù)2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
-
Command+B
編譯后绍刮,使用自己的clang
和HTPlugin
編譯ViewController.m
文件:
- 可以發(fā)現(xiàn)温圆,我們已
成功
過濾系統(tǒng)消息
,只留下我們自己的代碼信息。
2.2.5 定位
代碼錯誤
,添加錯誤提示
- 添加
錯誤修飾符
的定位
和提示
的代碼:(新增8)
#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;
// 1. 聲明命名空間杨箭,創(chuàng)建插件
namespace HTPlugin {
// 5. 自定義HTMatchCallBack(過濾器回調(diào)類) 繼承自MatchFinder的MatchCallBack
class HTMatchCallBack: public MatchFinder::MatchCallback {
private:
// 6.2 添加屬性:編譯器實例
CompilerInstance &compilerInstance;
// 7. 判斷是否是用戶代碼的函數(shù)(通過文件地址判斷)
bool isUserSourceCode(const string fileName) {
// 空,不是用戶的
if (fileName.empty()) return false;
// Xcode中的源碼锅移,都是系統(tǒng)的
if (fileName.find("/Applications/Xcode.app/") == 0) return false;
// 其他情況,都是用戶的
return true;
}
// 8. 判斷是否應(yīng)該copy修飾
bool isShouldUseCopy(const string typeStr) {
if (typeStr.find("NSString") != string::npos ||
typeStr.find("NSArray") != string::npos ||
typeStr.find("NSDictionary") != string::npos) {
return true;
}
return false;
}
public:
// 6.3 聲明構(gòu)造方法饱搏,入?yún)⑿略鯟ompilerInstance編譯器實例,并賦值給compilerInstance
HTMatchCallBack(CompilerInstance &CI):compilerInstance(CI) { }
// 5.2 重寫run方法 (回調(diào)的執(zhí)行函數(shù))
void run(const MatchFinder::MatchResult &Result) {
// 通過Result拿到了所有節(jié)點, 通過自定義的節(jié)點標(biāo)識非剃,拿到我們標(biāo)記的所有節(jié)點
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
// 過濾空情況
if (propertyDecl) {
// 6.4 打印文件名稱
string fileName = compilerInstance.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
// 7.1 不是用戶的文件,就不往下打印
if (!isUserSourceCode(fileName)) return;
// 拿到類型窍帝,轉(zhuǎn)string努潘,打印
string typeStr = propertyDecl->getType().getAsString();
// 8.1 找出應(yīng)該使用copy的屬性
if (isShouldUseCopy(typeStr)) {
// 拿到節(jié)點描述 (ObjCPropertyAttribute::Kind 是枚舉,其中kind_copy = 0x20)
ObjCPropertyAttribute::Kind attrKind = propertyDecl->getPropertyAttributes();
// 當(dāng)前修飾符不為copy坤学,提示
if (!(attrKind & ObjCPropertyAttribute::kind_copy)) {
// 診斷引擎
DiagnosticsEngine &diag = compilerInstance.getDiagnostics();
// Report 報告
// 參數(shù)1: 定位位置
// 參數(shù)2: 設(shè)置等級和提示文案
diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦使用copy!!!"))<<typeStr;
}
}
}
}
};
// 4. 自定義HTConsumer,繼承ASTConsumer
// 進(jìn)入`ASTConsumer`报慕,查看它的結(jié)構(gòu)深浮,有很多選擇
// 本例重載[HandleTopLevelDecl]頂級節(jié)點的解析和[HandleTranslationUnit]文件解析結(jié)束回調(diào)
class HTConsumer: public ASTConsumer {
private:
// 4.3 添加屬性:AST節(jié)點查找過濾器
MatchFinder matcher;
// 5.1 添加屬性:MatchFinder過濾器的回調(diào)函數(shù)
HTMatchCallBack callback;
public:
// 4.5 聲明構(gòu)造方法(出廠就添加一個MatchFinder過濾器)
// 6.4 入?yún)⑿略觯壕幾g器實例CI,并賦值給callback
HTConsumer(CompilerInstance &CI):callback(CI) {
// 添加MatchFinder (參數(shù)1: 過濾的節(jié)點, 參數(shù)2眠冈, 過濾后的回調(diào))
// 查看MatchFinder中的MatchCallBack的結(jié)構(gòu)飞苇,仿寫一個回調(diào)函數(shù)
// 此處,過濾`objcPropertyDecl`屬性節(jié)點蜗顽,手動添加節(jié)點標(biāo)識`objcPropertyDecl`
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
// 4.1 頂級節(jié)點解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
// cout<<"解析中..."<<endl;
return true;
}
// 4.2 單個文件解析結(jié)束
void HandleTranslationUnit(ASTContext &Ctx) {
// cout<<"文件解析完畢布卡!"<<endl;
// 4.4 解析完成后,將語法樹給到`matcher`過濾器
matcher.matchAST(Ctx);
}
};
// 2. 創(chuàng)建插件(繼承PluginASTAction 實現(xiàn)自定義的ASTAction)
// 【目的】讀取AST語法樹的所有節(jié)點雇盖。
// 【如何重寫】 進(jìn)入`PluginASTAction`忿等,查看它的結(jié)構(gòu),重載[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.2 解析成功崔挖,就返回true贸街。 我們直接寫true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.3 創(chuàng)建一個語法樹對象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定義的HTConsumer庵寞,繼承自ASTConsumer。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
// 6.1 存儲CompilerInstance實例(它是每個編譯文件薛匪,可以通過它讀取編譯文件的路徑捐川,剔除系統(tǒng)文件的干擾)
return unique_ptr<HTConsumer>(new HTConsumer(CI));
}
};
}
// 3. 注冊插件 (參數(shù)1:插件名稱, 參數(shù)2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
-
Command+B
編譯后逸尖,使用自己的clang
和HTPlugin
編譯ViewController.m
文件:
image.png
2.2.6 Xcode集成
自定義插件
1. Demo
工程添加自定義插件 Build Settings
->Other C Flags
添加:
-Xclang -load -Xclang /Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib -Xclang -add-plugin -Xclang HTPlugin
/Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib
是自己的HTPlugin.dylib
絕對路徑
2. Command + B
編譯古沥,報錯:
-
Clang插件
需要使用對應(yīng)的版本
去加載
,版本不一致
導(dǎo)致的編譯錯誤
:
image.png -
在
Build Settings
欄目新增兩項
用戶定義的設(shè)置:
image.png 添加
CC
和CXX
兩個設(shè)置:
CC
: 填寫clang
絕對路徑
CXX
:填寫clang++
絕對路徑
image.png
image.png
- 在
Build Settings
搜索index
:
image.png
2.2.7 編譯成功
Command+B
編譯娇跟,編譯成功
渐白,查看ViewController.m
文件:
-
修改
name
的修飾符為copy
,Command+B
編譯后看逞频,name
已經(jīng)不報錯
了纯衍。
image.png -
恭喜你。 成功了苗胀!
image.png
通過這個小插件
襟诸,應(yīng)該對語法樹
、編譯流程
基协,有了更深刻
的認(rèn)識
歌亲。??