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

OC底層原理 學習大綱

  • 上一節,我們熟悉了LLVM完整流程

本節:

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

1. 配置LLVM環境

?? ?? ?? 【注意】

  1. LLVM源碼2.29G編譯后文件將近30G,請確保電腦硬盤空間足夠
  2. 編譯時,電腦溫度會飆升90多度CPU資源占滿,請用空調伺候著,可能黑屏
  3. 編譯時間長達1個多小時,請合理安排時間。

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

1.1 LLVM下載

  • clangclang-tools-extracompiler-rtlibcxxlibcxxabillvm五個庫:(我下載的都是11.0.0版本的)
    image.png
  • 解壓移除名稱中的版本號
image.png
  • 按以下順序將文件夾移到指定位置
  1. clang-tools-extra移到clang文件夾中的clang/tools文件中
  2. clang文件夾移到llvm/tools
  3. compiler-rtlibcxxlibcxxabi都移到llvm/projects
    image.png
image.png

1.2 安裝cmake

  • 查看brew列表,檢查是否安裝過cmake,如果有,就跳過此步驟
brew list
  • 如果沒有,就使用brew安裝:
brew install cmake
  • 如果報權限錯誤,可sudo chown -R `whoami`:admin /usr/local/share放開權限
    image.png

1.3 編譯

  • llvm同級目錄創建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

選擇手動創建Schemes

image.png

  • 添加clangclangTooling兩個Target,并完成兩個target的編譯
    (此處可能需要1小時cpu占滿,請適當給電腦降溫??)
image.png
  • 編譯成功后,我們的準備工作完成了。可以正式開始插件開發

2. 自定義插件

2.1 添加插件

  • 我們在llvm/tools/clang/tools文件夾中,創建HTPlugin文件夾,并新增兩個文件:
    image.png
  1. CMakeLists.txt內容: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
  • 創建好后,我們回到build文件夾,cmake -G Xcode ../llvm 重新編譯生成Xcode文件。
開始編譯
編譯完成

ps: 我的電腦系統版本是:

image.png

  • 打開build文件夾中新生成的LLVM.xcodeproj:
  • Command + 鼠標左鍵點擊文件夾,折疊所有文件夾。 在Loadable modules中,可以看到我們自己創建的HTPlugin文件夾:
    image.png
  • 搜索選擇 HTPlugin
    image.png

2.2 書寫插件

2.2.1 體驗單文件頂級節點解析

1. 在HTPlugn.cpp文件中加入以下代碼

// 1. 聲明命名空間,創建插件
namespace HTPlugin {
    
    // 4. 自定義HTConsumer,繼承ASTConsumer
    // 進入`ASTConsumer`,查看它的結構,有很多選擇
    //    本例重載[HandleTopLevelDecl]頂級節點的解析和[HandleTranslationUnit]文件解析結束回調
    class HTConsumer: public ASTConsumer {
    private:
    public:
        
        // 4.1 頂級節點解析中
        bool HandleTopLevelDecl(DeclGroupRef D) {
            cout<<"解析中..."<<endl;
            return true;
        }
        
        // 4.2 單個文件解析結束
        void HandleTranslationUnit(ASTContext &Ctx) {
            cout<<"文件解析完畢!"<<endl;
        }
        
    };

    // 2. 創建插件(繼承PluginASTAction 實現自定義的ASTAction)
    // 【目的】讀取AST語法樹的所有節點。
    // 【如何重寫】 進入`PluginASTAction`,查看它的結構,重載[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 創建一個語法樹對象
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                  StringRef InFile){
            // 使用自定義的HTConsumer,繼承自ASTConsumer。
//            return unique_ptr<ASTConsumer>(new ASTConsumer());
            return unique_ptr<HTConsumer>(new HTConsumer());
        }
    };

}

// 3. 注冊插件 (參數1:插件名稱, 參數2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");

2. Command+B 編譯一次
3. 創建一個測試文件demo.m

image.png

  • demo.m內容:
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/
    使用自己本機SDK絕對路徑。 (我這是14.2)
    image.png
  • /Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib:
    自己插件編譯后HTPlugin.dylib插件動態庫絕對路徑,在build/Debug/lib/文件夾中:
    image.png
  • 指定使用HTPlugin 編譯demo.m文件
image.png

5. 編譯完成后,會生成demo.o文件。是一個object目標文件(不懂目標文件,可回顧上一節

image.png

2.2.2 觀察屬性修飾符節點名

1. 創建工程

  • ClangDemo文件夾中,創建demo工程:

    image.png

  • ViewController.m文件中加入測試代碼

@interface ViewController()

// NSString、NSArray、NSDictionary 應使用Copy修飾
@property (nonatomic, strong) NSString * name;   
@property (nonatomic, strong) NSArray * arrs;
@property (nonatomic, strong) NSDictionary *dicts;

@end

2. 使用系統clang編譯ViewController.m文件

  • cdViewController.m的文件夾,使用系統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
  • 可以發現在AST語法樹中,ObjCPropertyDecl屬性節點
2.2.3 過濾ObjCPropertyDecl節點
  • 修改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. 聲明命名空間,創建插件
namespace HTPlugin {
    
    // 5. 自定義HTMatchCallBack(過濾器回調類) 繼承自MatchFinder的MatchCallBack
    class HTMatchCallBack: public MatchFinder::MatchCallback {
    public:
        // 5.2 重寫run方法
        void run(const MatchFinder::MatchResult &Result) {
            
            // 通過Result拿到了所有節點, 通過自定義的節點標識,拿到我們標記的所有節點
            const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
            
            // 過濾空情況
            if (propertyDecl) {
                // 拿到類型,轉string,打印
                string typeStr = propertyDecl->getType().getAsString();
                cout<<"--- 拿到了: 【"<<typeStr<<"】 ----"<<endl;
            }
            
        }
    };


    // 4. 自定義HTConsumer,繼承ASTConsumer
    // 進入`ASTConsumer`,查看它的結構,有很多選擇
    //    本例重載[HandleTopLevelDecl]頂級節點的解析和[HandleTranslationUnit]文件解析結束回調
    class HTConsumer: public ASTConsumer {
    private:
        // 4.3 添加屬性:AST節點查找過濾器
        MatchFinder matcher;
        // 5.1 添加屬性:MatchFinder過濾器的回調函數
        HTMatchCallBack callback;
    public:
        
        // 4.5 聲明構造方法(出廠就添加一個MatchFinder過濾器)
        HTConsumer() {
            // 添加MatchFinder (參數1: 過濾的節點, 參數2, 過濾后的回調)
            //    查看MatchFinder中的MatchCallBack的結構,仿寫一個回調函數
            // 此處,過濾`objcPropertyDecl`屬性節點,手動添加節點標識`objcPropertyDecl`
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
        }
        
        // 4.1 頂級節點解析中
        bool HandleTopLevelDecl(DeclGroupRef D) {
            cout<<"解析中..."<<endl;
            return true;
        }
        
        // 4.2 單個文件解析結束
        void HandleTranslationUnit(ASTContext &Ctx) {
            cout<<"文件解析完畢!"<<endl;
            // 4.4 解析完成后,將語法樹給到`matcher`過濾器
            matcher.matchAST(Ctx);
        }
        
    };

    // 2. 創建插件(繼承PluginASTAction 實現自定義的ASTAction)
    // 【目的】讀取AST語法樹的所有節點。
    // 【如何重寫】 進入`PluginASTAction`,查看它的結構,重載[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 創建一個語法樹對象
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                  StringRef InFile){
            // 使用自定義的HTConsumer,繼承自ASTConsumer。
            // return unique_ptr<ASTConsumer>(new ASTConsumer());
            return unique_ptr<HTConsumer>(new HTConsumer());
        }
    };

}

// 3. 注冊插件 (參數1:插件名稱, 參數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項目,所以創建Demo時,默認創建了MacOS工程,然后我使用了iPhoneSimulator14.2.sdkiOS SDK來編譯。一直報'Cocoa/Cocoa.h' file not found
    image.png

出現Cocoa/Cocoa.h的問題,都是MacOS的問題,iOS使用Fundation庫。macOS使用Cocoa庫。


  • 可以看到打印了非常多的屬性節點信息:

    image.png

  • 其中大部分都是系統文件屬性,我們需要排除這些,定位自己代碼屬性

2.2.4 過濾系統文件節點

可以通過編譯器實例(文件)CompilerInstance,讀取每個實例路徑。代碼如下:(新增6)

// 1. 聲明命名空間,創建插件
namespace HTPlugin {
    
    // 5. 自定義HTMatchCallBack(過濾器回調類) 繼承自MatchFinder的MatchCallBack
    class HTMatchCallBack: public MatchFinder::MatchCallback {
    private:
        // 6.2 添加屬性:編譯器實例
        CompilerInstance &compilerInstance;
    public:
        
        // 6.3 聲明構造方法,入參新增CompilerInstance編譯器實例,并賦值給compilerInstance
        HTMatchCallBack(CompilerInstance &CI):compilerInstance(CI) { }
        
        // 5.2 重寫run方法 (回調的執行函數)
        void run(const MatchFinder::MatchResult &Result) {
            
            // 通過Result拿到了所有節點, 通過自定義的節點標識,拿到我們標記的所有節點
            const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
            
            // 過濾空情況
            if (propertyDecl) {
                
                // 6.4 打印文件名稱
                string fileName = compilerInstance.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
                
                // 拿到類型,轉string,打印
                string typeStr = propertyDecl->getType().getAsString();
                
                cout<<"--- 拿到了: 【"<<typeStr<<"】 ----文件路徑:"<<fileName<<endl;
            }
            
        }
    };

    // 4. 自定義HTConsumer,繼承ASTConsumer
    // 進入`ASTConsumer`,查看它的結構,有很多選擇
    //    本例重載[HandleTopLevelDecl]頂級節點的解析和[HandleTranslationUnit]文件解析結束回調
    class HTConsumer: public ASTConsumer {
    private:
        // 4.3 添加屬性:AST節點查找過濾器
        MatchFinder matcher;
        // 5.1 添加屬性:MatchFinder過濾器的回調函數
        HTMatchCallBack callback;
    public:
        // 4.5 聲明構造方法(出廠就添加一個MatchFinder過濾器)
        //  6.4 入參新增:編譯器實例CI,并賦值給callback
        HTConsumer(CompilerInstance &CI):callback(CI) {
            // 添加MatchFinder (參數1: 過濾的節點, 參數2, 過濾后的回調)
            //    查看MatchFinder中的MatchCallBack的結構,仿寫一個回調函數
            // 此處,過濾`objcPropertyDecl`屬性節點,手動添加節點標識`objcPropertyDecl`
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
        }
        
        // 4.1 頂級節點解析中
        bool HandleTopLevelDecl(DeclGroupRef D) {
            cout<<"解析中..."<<endl;
            return true;
        }
        
        // 4.2 單個文件解析結束
        void HandleTranslationUnit(ASTContext &Ctx) {
            cout<<"文件解析完畢!"<<endl;
            // 4.4 解析完成后,將語法樹給到`matcher`過濾器
            matcher.matchAST(Ctx);
        }
        
    };

    // 2. 創建插件(繼承PluginASTAction 實現自定義的ASTAction)
    // 【目的】讀取AST語法樹的所有節點。
    // 【如何重寫】 進入`PluginASTAction`,查看它的結構,重載[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 創建一個語法樹對象
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                  StringRef InFile){
            // 使用自定義的HTConsumer,繼承自ASTConsumer。
            // return unique_ptr<ASTConsumer>(new ASTConsumer());
            // 6.1 存儲CompilerInstance實例(它是每個編譯文件,可以通過它讀取編譯文件的路徑,剔除系統文件的干擾)
            return unique_ptr<HTConsumer>(new HTConsumer(CI));
        }
    };

}

// 3. 注冊插件 (參數1:插件名稱, 參數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
  • 可以發現,所有系統文件,地址都是以/Applications/Xcode.app/開頭。

    image.png

  • 通過區分文件地址,可以剔除所有系統文件:(新增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. 聲明命名空間,創建插件
namespace HTPlugin {
    
    // 5. 自定義HTMatchCallBack(過濾器回調類) 繼承自MatchFinder的MatchCallBack
    class HTMatchCallBack: public MatchFinder::MatchCallback {
    private:
        // 6.2 添加屬性:編譯器實例
        CompilerInstance &compilerInstance;
        
        // 7. 判斷是否是用戶代碼的函數(通過文件地址判斷)
        bool isUserSourceCode(const string fileName) {
            
            // 空,不是用戶的
            if (fileName.empty()) return false;
            
            // Xcode中的源碼,都是系統的
            if (fileName.find("/Applications/Xcode.app/") == 0) return false;
            
            // 其他情況,都是用戶的
            return true;
            
        }
    public:
        
        // 6.3 聲明構造方法,入參新增CompilerInstance編譯器實例,并賦值給compilerInstance
        HTMatchCallBack(CompilerInstance &CI):compilerInstance(CI) { }
        
        // 5.2 重寫run方法 (回調的執行函數)
        void run(const MatchFinder::MatchResult &Result) {
            
            // 通過Result拿到了所有節點, 通過自定義的節點標識,拿到我們標記的所有節點
            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;
                
                // 拿到類型,轉string,打印
                string typeStr = propertyDecl->getType().getAsString();
                
                cout<<"--- 拿到了: 【"<<typeStr<<"】 ----文件路徑:"<<fileName<<endl;
            }
            
        }
    };

    // 4. 自定義HTConsumer,繼承ASTConsumer
    // 進入`ASTConsumer`,查看它的結構,有很多選擇
    //    本例重載[HandleTopLevelDecl]頂級節點的解析和[HandleTranslationUnit]文件解析結束回調
    class HTConsumer: public ASTConsumer {
    private:
        // 4.3 添加屬性:AST節點查找過濾器
        MatchFinder matcher;
        // 5.1 添加屬性:MatchFinder過濾器的回調函數
        HTMatchCallBack callback;
    public:
        // 4.5 聲明構造方法(出廠就添加一個MatchFinder過濾器)
        //  6.4 入參新增:編譯器實例CI,并賦值給callback
        HTConsumer(CompilerInstance &CI):callback(CI) {
            // 添加MatchFinder (參數1: 過濾的節點, 參數2, 過濾后的回調)
            //    查看MatchFinder中的MatchCallBack的結構,仿寫一個回調函數
            // 此處,過濾`objcPropertyDecl`屬性節點,手動添加節點標識`objcPropertyDecl`
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
        }
        
        // 4.1 頂級節點解析中
        bool HandleTopLevelDecl(DeclGroupRef D) {
            cout<<"解析中..."<<endl;
            return true;
        }
        
        // 4.2 單個文件解析結束
        void HandleTranslationUnit(ASTContext &Ctx) {
            cout<<"文件解析完畢!"<<endl;
            // 4.4 解析完成后,將語法樹給到`matcher`過濾器
            matcher.matchAST(Ctx);
        }
        
    };

    // 2. 創建插件(繼承PluginASTAction 實現自定義的ASTAction)
    // 【目的】讀取AST語法樹的所有節點。
    // 【如何重寫】 進入`PluginASTAction`,查看它的結構,重載[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 創建一個語法樹對象
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                  StringRef InFile){
            // 使用自定義的HTConsumer,繼承自ASTConsumer。
            // return unique_ptr<ASTConsumer>(new ASTConsumer());
            // 6.1 存儲CompilerInstance實例(它是每個編譯文件,可以通過它讀取編譯文件的路徑,剔除系統文件的干擾)
            return unique_ptr<HTConsumer>(new HTConsumer(CI));
        }
    };

}

// 3. 注冊插件 (參數1:插件名稱, 參數2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
  • Command+B 編譯后,使用自己的clangHTPlugin編譯ViewController.m文件:
image.png
  • 可以發現,我們已成功過濾系統消息,只留下我們自己的代碼信息。
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. 聲明命名空間,創建插件
namespace HTPlugin {
    
    // 5. 自定義HTMatchCallBack(過濾器回調類) 繼承自MatchFinder的MatchCallBack
    class HTMatchCallBack: public MatchFinder::MatchCallback {
    private:
        // 6.2 添加屬性:編譯器實例
        CompilerInstance &compilerInstance;
        
        // 7. 判斷是否是用戶代碼的函數(通過文件地址判斷)
        bool isUserSourceCode(const string fileName) {
            
            // 空,不是用戶的
            if (fileName.empty()) return false;
            
            // Xcode中的源碼,都是系統的
            if (fileName.find("/Applications/Xcode.app/") == 0) return false;
            
            // 其他情況,都是用戶的
            return true;
            
        }
        
        // 8. 判斷是否應該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 聲明構造方法,入參新增CompilerInstance編譯器實例,并賦值給compilerInstance
        HTMatchCallBack(CompilerInstance &CI):compilerInstance(CI) { }
        
        // 5.2 重寫run方法 (回調的執行函數)
        void run(const MatchFinder::MatchResult &Result) {
            
            // 通過Result拿到了所有節點, 通過自定義的節點標識,拿到我們標記的所有節點
            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;
                
                // 拿到類型,轉string,打印
                string typeStr = propertyDecl->getType().getAsString();
                
                // 8.1 找出應該使用copy的屬性
                if (isShouldUseCopy(typeStr)) {
                    
                    // 拿到節點描述 (ObjCPropertyAttribute::Kind 是枚舉,其中kind_copy = 0x20)
                    ObjCPropertyAttribute::Kind attrKind = propertyDecl->getPropertyAttributes();
                    
                    // 當前修飾符不為copy,提示
                    if (!(attrKind & ObjCPropertyAttribute::kind_copy)) {
                        
                        // 診斷引擎
                        DiagnosticsEngine &diag = compilerInstance.getDiagnostics();
                        
                        // Report 報告
                        // 參數1: 定位位置
                        // 參數2: 設置等級和提示文案
                        diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦使用copy!!!"))<<typeStr;
                    }
                    
                }
                
            }
            
        }
    };

    // 4. 自定義HTConsumer,繼承ASTConsumer
    // 進入`ASTConsumer`,查看它的結構,有很多選擇
    //    本例重載[HandleTopLevelDecl]頂級節點的解析和[HandleTranslationUnit]文件解析結束回調
    class HTConsumer: public ASTConsumer {
    private:
        // 4.3 添加屬性:AST節點查找過濾器
        MatchFinder matcher;
        // 5.1 添加屬性:MatchFinder過濾器的回調函數
        HTMatchCallBack callback;
    public:
        // 4.5 聲明構造方法(出廠就添加一個MatchFinder過濾器)
        //  6.4 入參新增:編譯器實例CI,并賦值給callback
        HTConsumer(CompilerInstance &CI):callback(CI) {
            // 添加MatchFinder (參數1: 過濾的節點, 參數2, 過濾后的回調)
            //    查看MatchFinder中的MatchCallBack的結構,仿寫一個回調函數
            // 此處,過濾`objcPropertyDecl`屬性節點,手動添加節點標識`objcPropertyDecl`
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
        }
        
        // 4.1 頂級節點解析中
        bool HandleTopLevelDecl(DeclGroupRef D) {
//            cout<<"解析中..."<<endl;
            return true;
        }
        
        // 4.2 單個文件解析結束
        void HandleTranslationUnit(ASTContext &Ctx) {
//            cout<<"文件解析完畢!"<<endl;
            // 4.4 解析完成后,將語法樹給到`matcher`過濾器
            matcher.matchAST(Ctx);
        }
        
    };

    // 2. 創建插件(繼承PluginASTAction 實現自定義的ASTAction)
    // 【目的】讀取AST語法樹的所有節點。
    // 【如何重寫】 進入`PluginASTAction`,查看它的結構,重載[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 創建一個語法樹對象
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                  StringRef InFile){
            // 使用自定義的HTConsumer,繼承自ASTConsumer。
            // return unique_ptr<ASTConsumer>(new ASTConsumer());
            // 6.1 存儲CompilerInstance實例(它是每個編譯文件,可以通過它讀取編譯文件的路徑,剔除系統文件的干擾)
            return unique_ptr<HTConsumer>(new HTConsumer(CI));
        }
    };

}

// 3. 注冊插件 (參數1:插件名稱, 參數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插件需要使用對應的版本加載版本不一致導致的編譯錯誤

    image.png

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

    image.png

  • 添加CCCXX兩個設置:

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已經不報錯了。

    image.png

  • 恭喜你。 成功了!

    image.png

通過這個小插件,應該對語法樹編譯流程,有了更深刻認識。??

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容