iOS-底層探索29:自定義Clang插件

iOS 底層探索 文章匯總

目錄


一池颈、LLVM編譯

1.1凡涩、LLVM 下載

當(dāng)前系統(tǒng)環(huán)境如下:
OS:macOS Big Sur搂鲫, 芯片:Apple Silicon M1
根據(jù)Mac當(dāng)前系統(tǒng)下載LLVM Releases源碼,保存的路徑中不要包含空格之類的字符潜必,這里選擇當(dāng)前最新的llvmorg-11.0.0源碼靴姿。
https://github.com/llvm/llvm-project/releases/tag/llvmorg-11.0.0
下載 Source code(zip)

1.2、LLVM編譯

由于最新的L LVM只支持cmake來編譯了磁滚,我們還需要安裝cmake佛吓。

安裝 cmake
  • 查看brew是否安裝cmake如果有就跳過下面的步驟
brew list
  • 通過brew安裝cmake
brew install cmake
1.2.1、通過Xcode編譯LLVM

cd llvm-project文件夾
mkdir build_xcode
cd build_xcode
cmake -G Xcode ../llvm

cmake完成后會在build_xcode中出現(xiàn)Xcode項(xiàng)目
Xcode中使用Automatically Create Schemes然后選擇ALL_BUILD Schemes編譯項(xiàng)目(Xcode能編譯成功)

1.2.2恨旱、通過ninja編譯LLVM
  • 使用ninja進(jìn)行編譯則還需要安裝ninja辈毯。使用brew install ninja命令即可安裝ninja
  • llvm源碼文件夾中新建一個build_ninja目錄搜贤, 最終會在build_ninja目錄下生成build.ninja谆沃。
  • llvm源碼文件夾中新建一個llvm_release目錄,最終編譯文件會在llvm_release文件夾路徑下仪芒。
cd build_ninja
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX= 安裝路徑(本機(jī)為/Users/xxx/xxx/LLVM/llvm_release)
注意DCMAKE_INSTALL_PREFIX后面不能有空格唁影。
  • 依次執(zhí)行編譯、安裝指令掂名。
ninja
ninja install(這步執(zhí)行報(bào)錯据沈,可能是ninja未適配M1)

二、創(chuàng)建自定義Clang插件

2.1饺蔑、創(chuàng)建插件

1锌介、在/llvm-project/clang/tools目錄下新建插件CJPlugin

2 、修改/llvm-project/clang/tools目錄下的CMakeLists.txt文件猾警,新增add_clang_subdirectory(CJPlugin)

image.png

3孔祸、在CJPlugin目錄下新建一個名為CJPlugin.cpp的文件和CMakeLists.txt的文件。在CMakeLists.txt中添加如下代碼:

add_llvm_library( CJPlugin MODULE BUILDTREE_ONLY
  CJPlugin.cpp
)

4发皿、接下來利用cmake重新生成一下Xcode項(xiàng)目

cd build_xcode
cmake -G Xcode -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi" ../llvm //此命令會編譯clang目錄下的自定義插件

5崔慧、最后可以在LLVMXcode項(xiàng)目中可以看到Loadable modules目錄下有自己的Plugin目錄了。我們可以在這里面編寫插件代碼穴墅。

2.2惶室、編寫插件代碼

#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 CJPlugin {
class CJASTConsumer: public ASTConsumer {
public:
    // clang 解析完一個頂級的聲明的回調(diào)
    bool HandleTopLevelDecl(DeclGroupRef D) {
        cout<<"正在解析..."<<endl;
        return true;
    }
    
    // clang 解析完整個文件的回調(diào)
    void HandleTranslationUnit(ASTContext &context) {
        cout<<"文件解析完畢!"<<endl;
    }
};

// 繼承PluginASTAction實(shí)現(xiàn)我們自定義的Action
class CJASTAction: public PluginASTAction {
public:
    unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
        return unique_ptr<CJASTConsumer> (new CJASTConsumer);
    }
    
    bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &args) {
        return true;
    }
};

// 注冊插件
static FrontendPluginRegistry::Add<CJPlugin::CJASTAction> CJ("CJPlugin", "This is the description of the plugin");
}

2.3玄货、測試插件代碼

  • 編譯Xcode項(xiàng)目
  • 編寫測試源碼
vi hello.m

int sum (int a);
int a;
int sum (int a) {
       int b = 10;
       return a + b + 10;
}

int sum2 (int a, int b) {
       int c = 10;
       return a + b + c;
}
  • 使用自己編譯的clang文件路徑和插件路徑測試
命令如下:

自己編譯的clang文件路徑 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk -Xclang -load -Xclang 插件(.dylib)路徑 -Xclang -add-plugin -Xclang CJPlugin(插件名) -c 源碼路徑

測試結(jié)果:

2.4皇钞、Xcode集成插件

1、新建測試項(xiàng)目Test_LLVM誉结,并添加如下代碼:
@interface ViewController ()

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSArray *arrs;

@end

我們接下來使用我們自定義的clang插件提示屬性定義存在的問題鹅士。

使用原生的clang解析ViewController.m并生成AST

cd 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

AST中我們發(fā)現(xiàn)屬性定義節(jié)點(diǎn)名為ObjCPropertyDecl,因此我們自定clang中需要過濾出ObjCPropertyDecl節(jié)點(diǎn)惩坑。

2掉盅、修改代碼過濾出ViewController.m中的ObjCPropertyDecl節(jié)點(diǎn)
namespace CJPlugin {

class CJMatchCallback: public MatchFinder::MatchCallback {
private:
    CompilerInstance &CI;
    // 排除系統(tǒng)的屬性
    bool isUserSourceCode(const string filename) {
        if (filename.empty()) return false;
        if (filename.find("/Applications/Xcode.app/") == 0) return false;
        return true;
    }

    // 判斷是否使用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:
    CJMatchCallback(CompilerInstance &CI) :CI(CI) {}
    void run(const MatchFinder::MatchResult &Result) {
        // 通過Result獲取到節(jié)點(diǎn)
        const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
        // 獲取文件名稱
        string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
        
        if (propertyDecl && isUserSourceCode(fileName)) {
            string typeStr = propertyDecl->getType().getAsString();
            cout<<"拿到屬性:"<<typeStr<<"屬于文件:"<<fileName<<endl;
            // 拿到節(jié)點(diǎn)的描述信息
            ObjCPropertyAttribute::Kind attrKind = propertyDecl->getPropertyAttributes();
            if (propertyDecl->getTypeSourceInfo() && isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyAttribute::kind_copy)) {
                cout<<"推薦使用 copy 修飾"<<endl;
            }
        }
    }
};

class CJASTConsumer: public ASTConsumer {
private:
    MatchFinder matcher;// AST節(jié)點(diǎn)查找過濾器
    CJMatchCallback callback;
public:
    CJASTConsumer(CompilerInstance &CI) :callback(CI) {
        // 添加一個MatchFinder去匹配objcPropertyDecl節(jié)點(diǎn)
        // 回調(diào)在CJMatchCallback中的run方法中
        matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);//綁定標(biāo)記"objcPropertyDecl"和取的時(shí)候一致即可
    }
    // clang 解析完一個頂級的聲明的回調(diào)
    bool HandleTopLevelDecl(DeclGroupRef D) {
        cout<<"正在解析..."<<endl;
        return true;
    }
    
    // clang 解析完整個文件的回調(diào)
    void HandleTranslationUnit(ASTContext &context) {
        matcher.matchAST(context);
        cout<<"文件解析完畢也拜!"<<endl;
    }
};

// 繼承PluginASTAction實(shí)現(xiàn)我們自定義的Action
class CJASTAction: public PluginASTAction {
public:
    unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef iFile) {
        return unique_ptr<CJASTConsumer> (new CJASTConsumer(CI));//CI用于戶過濾系統(tǒng)屬性
    }
    
    bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &args) {
        return true;
    }
};

// 注冊插件
static FrontendPluginRegistry::Add<CJPlugin::CJASTAction> CJ("CJPlugin", "This is the description of the plugin");
}

使用自定義clang解析ViewController.m代碼如下:

cd ViewController.m所在的目錄
/Users/ztkj/Projects/LLVM_Projects/llvm-project/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk  -Xclang -load -Xclang /Users/ztkj/Projects/LLVM_Projects/llvm-project/build_xcode/Debug/lib/CJPlugin.dylib -Xclang -add-plugin -Xclang CJPlugin -c ViewController.m 

測試結(jié)果:

3、添加Xcode顯示自定義提示的代碼
cout<<"推薦使用 copy 修飾"<<endl;
// 診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0推薦使用 copy 修飾"))<<typeStr;//%必須帶參數(shù)否者會報(bào)錯

測試結(jié)果:

4趾痘、加載插件

打開測試項(xiàng)目慢哈,在Build Settings -> Other C Flags添加如下內(nèi)容:

-Xclang -load -Xclang (.dylib)動態(tài)庫路徑 -Xclang -add-plugin -Xclang CJPlugin
5、設(shè)置編譯器
  • 由于Clang插件需要使用對應(yīng)的版本去加載永票,如果版本不一致就會導(dǎo)致編譯錯誤卵贱,如下圖所示:

  • Build Settings欄目中新增兩項(xiàng)用戶定義的設(shè)置

  • 分別是CCCXX
    CC對應(yīng)的是自己編譯的clang的絕對路徑
    CXX對應(yīng)的是自己編譯的clang++的絕對路徑
  • 接下來在Build Settings欄目中搜索index,將Enable Index-Wihle-Building Functionality的Default改為NO侣集。
6键俱、編譯Test_LLVM項(xiàng)目成功后即可看到Xcode顯示自定義clang插件中的提示

目前自定義的插件必須編譯后才能顯示提示,代碼修改后也不會自動更新提示世分,且還沒完善Fix功能编振。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市臭埋,隨后出現(xiàn)的幾起案子踪央,更是在濱河造成了極大的恐慌,老刑警劉巖瓢阴,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件畅蹂,死亡現(xiàn)場離奇詭異,居然都是意外死亡荣恐,警方通過查閱死者的電腦和手機(jī)液斜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叠穆,“玉大人旗唁,你說我怎么就攤上這事”允” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵讶请,是天一觀的道長祷嘶。 經(jīng)常有香客問我,道長夺溢,這世上最難降的妖魔是什么论巍? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮风响,結(jié)果婚禮上嘉汰,老公的妹妹穿的比我還像新娘。我一直安慰自己状勤,他們只是感情好鞋怀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布双泪。 她就那樣靜靜地躺著,像睡著了一般密似。 火紅的嫁衣襯著肌膚如雪焙矛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天残腌,我揣著相機(jī)與錄音村斟,去河邊找鬼。 笑死抛猫,一個胖子當(dāng)著我的面吹牛蟆盹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闺金,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼逾滥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了掖看?” 一聲冷哼從身側(cè)響起匣距,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哎壳,沒想到半個月后毅待,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡归榕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年尸红,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刹泄。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡外里,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出特石,到底是詐尸還是另有隱情盅蝗,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布姆蘸,位于F島的核電站墩莫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逞敷。R本人自食惡果不足惜狂秦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望推捐。 院中可真熱鬧裂问,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至戴甩,卻和暖如春符喝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甜孤。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工协饲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缴川。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓茉稠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親把夸。 傳聞我的和親對象是個殘疾皇子而线,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容