OC底層原理三十二:LLVM插件(Copy修飾符檢測)

OC底層原理 學(xué)習(xí)大綱

本節(jié):

  • 自定義Clang插件是實現(xiàn)Copy屬性修飾符檢測智能提示
    (最終的使用場景可能并沒那么通用嚎幸,但主要目的是徹底熟悉LLVM
  • 文末會附上最終代碼使用方法
  1. 配置LLVM環(huán)境
  2. 自定義插件
  • 如果你也跟我一樣充滿期待察滑。那我們就開始吧。

1. 配置LLVM環(huán)境

?? ?? ?? 【注意】

  1. LLVM源碼2.29G編譯后文件將近30G纳猫,請確保電腦硬盤空間足夠
  2. 編譯時竹捉,電腦溫度會飆升90多度芜辕,CPU資源占滿,請用空調(diào)伺候著块差,可能黑屏侵续;
  3. 編譯時間長達(dá)1個多小時倔丈,請合理安排時間。

如果以上3點状蜗,你確定能接受需五,那我們就開始吧。

1.1 LLVM下載

  • clang轧坎、clang-tools-extra宏邮、compiler-rtlibcxx眶根、libcxxabi蜀铲、llvm五個庫:(我下載的都是11.0.0版本的)
    image.png
  • 解壓移除名稱中的版本號
image.png
  • 按以下順序?qū)?code>文件夾移到指定位置
  1. clang-tools-extra移到clang文件夾中的clang/tools文件中
  2. clang文件夾移到llvm/tools
  3. compiler-rt边琉、libcxx属百、libcxxabi都移到llvm/projects
    image.png
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 

注意:

    1. build文件夾是存放cmake生成的Xcode文件的扩氢。放哪里都可以。
    1. cmake編譯的對象是llvm文件爷辱。所以使用cmake -G Xcode ../llvm編譯并生成Xcode文件時录豺,請核對llvm文件路徑
  • 成功之后饭弓,可以看到生成的Xcode文件:

    image.png

  • 打開LLVM.xcodeproj

選擇手動創(chuàng)建Schemes

image.png

  • 添加clangclangTooling兩個Target双饥,并完成兩個target的編譯
    (此處可能需要1小時cpu占滿弟断,請適當(dāng)給電腦降溫??)
image.png
  • 編譯成功后咏花,我們的準(zhǔn)備工作完成了》浚可以正式開始插件開發(fā)

2. 自定義插件

2.1 添加插件

  • 我們在llvm/tools/clang/tools文件夾中昏翰,創(chuàng)建HTPlugin文件夾,并新增兩個文件:
    image.png
  1. CMakeLists.txt內(nèi)容:add_llvm_library( HTPlugin MODULE BUILDTREE_ONLY HTPlugin.cpp)
  2. HTPlugin.cpp:空文件
    image.png
  • llvm/tools/clang/tools文件夾的CMakeLists.txt文件尾部刘急,加上add_clang_subdirectory(HTPlugin)
image.png
  • 創(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

image.png

  • 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. cdClangDemo文件夾,使用我們的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文件
image.png

5. 編譯完成后解虱,會生成demo.o文件攘须。是一個object目標(biāo)文件(不懂目標(biāo)文件殴泰,可回顧上一節(jié)

image.png

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文件

  • cdViewController.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
image.png
  • 可以發(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編譯
  • cdllvm/ClangDemo/Demo/Demo文件夾印蔬。 使用我們自己的clangHTPlugin插件,編譯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.sdkiOS 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 編譯后映琳,使用自己的clangHTPlugin編譯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 編譯后绍刮,使用自己的clangHTPlugin編譯ViewController.m文件:
image.png
  • 可以發(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 編譯后逸尖,使用自己的clangHTPlugin編譯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絕對路徑

image.png

2. Command + B編譯古沥,報錯

  • Clang插件需要使用對應(yīng)的版本加載版本不一致導(dǎo)致的編譯錯誤

    image.png

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

    image.png

  • 添加CCCXX兩個設(shè)置:

CC : 填寫clang絕對路徑
CXX:填寫clang++絕對路徑

image.png

image.png

  • Build Settings搜索index:
    image.png
2.2.7 編譯成功

Command+B編譯娇跟,編譯成功渐白,查看ViewController.m文件:

image.png
  • 修改name的修飾符為copyCommand+B編譯后看逞频,name已經(jīng)不報錯了纯衍。

    image.png

  • 恭喜你。 成功了苗胀!

    image.png

通過這個小插件襟诸,應(yīng)該對語法樹編譯流程基协,有了更深刻認(rèn)識歌亲。??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市澜驮,隨后出現(xiàn)的幾起案子陷揪,更是在濱河造成了極大的恐慌,老刑警劉巖杂穷,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悍缠,死亡現(xiàn)場離奇詭異,居然都是意外死亡耐量,警方通過查閱死者的電腦和手機(jī)飞蚓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來廊蜒,“玉大人趴拧,你說我怎么就攤上這事∩蕉#” “怎么了著榴?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長屁倔。 經(jīng)常有香客問我脑又,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任挂谍,我火速辦了婚禮叔壤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘口叙。我一直安慰自己炼绘,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布妄田。 她就那樣靜靜地躺著俺亮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疟呐。 梳的紋絲不亂的頭發(fā)上脚曾,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機(jī)與錄音启具,去河邊找鬼本讥。 笑死,一個胖子當(dāng)著我的面吹牛鲁冯,可吹牛的內(nèi)容都是我干的拷沸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼薯演,長吁一口氣:“原來是場噩夢啊……” “哼撞芍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起跨扮,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤序无,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后衡创,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帝嗡,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年钧汹,在試婚紗的時候發(fā)現(xiàn)自己被綠了丈探。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡拔莱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隘竭,到底是詐尸還是另有隱情塘秦,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布动看,位于F島的核電站尊剔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏菱皆。R本人自食惡果不足惜须误,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一挨稿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧京痢,春花似錦奶甘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至方淤,卻和暖如春钉赁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背携茂。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工你踩, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讳苦。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓带膜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親医吊。 傳聞我的和親對象是個殘疾皇子钱慢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

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