iOS-底層原理30-clang插件開發

《iOS底層原理文章匯總》

clang插件開發

1.在下載的llvm/tools/clang/tools/CmakeLists.txt中增加要添加的插件名稱

CMakeLists增加插件名BCPlugin@2x.png

2.在此目錄下新建插件BCPlugin文件夾并新建CMakeLists.txt文件和BCPlugin.cpp文件

BCPlugin文件夾下CMakeLists@2x.png
  • 可以參考下LLVMHello插件下的CMakeLists.txt文件和Hello.cpp文件
LLVMHello@2x.png
  • 在build_xcode目錄下編譯執行cmake -G Xcode ../llvm,此時是增量編譯,不會耗時太久,編譯成功如下
增量編譯@2x.png
增量編譯成功@2x.png

若遇到編譯報錯,刪掉llvm文件夾下面的CMakeCache.txt文件,重新編譯即可

CMakellvm報錯@2x.png
-- The C compiler identification is AppleClang 11.0.3.11030032
-- The CXX compiler identification is AppleClang 11.0.3.11030032
-- The ASM compiler identification is Clang
-- Found assembler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Error at CMakeLists.txt:238 (message):
  In-source builds are not allowed.

  Please create a directory and run cmake from there, passing the path

  to this source directory as the last argument.

  This process created the file `CMakeCache.txt' and the directory
  `CMakeFiles'.

  Please delete them.


-- Configuring incomplete, errors occurred!
See also "/Users/cloud/Documents/iOS/1113/llvm/CMakeFiles/CMakeOutput.log".
See also "/Users/cloud/Documents/iOS/1113/llvm/CMakeFiles/CMakeError.log".

3.編譯完成后在build_xcode打開編譯好的llvm工程,會發現新建的BCPlugin插件,手動管理在Manage Schemes-> + -> BCPlugin

BCPlugin@2x.png
30.gif

4.編寫插件代碼

  • 1.拷貝頭文件信息
#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;
  • 2.新建BCASTAction繼承于PluginASTAction返回繼承于抽象類ASTConsumer的BCConsumer,獲取頂級節點解析和解析完成,cmd+B編譯成功,會在build_xcode/debug/lib目錄下生成BCPlugin.dylib
#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 BCPlugin {

    //自定義BCConsumer
    class BCConsumer:public ASTConsumer{
    public:
         //解析完一個頂級的聲明,就回調一次
         bool HandleTopLevelDecl(DeclGroupRef D){
            cout<<"正在解析。。。。"<<endl;
            return true;
         };
        
        //整個文件都解析完成的回調
         void HandleTranslationUnit(ASTContext &Ctx) {
             cout<<"文件解析完畢!"<<endl;
         }
    };

      //繼承PluginASTAction 實現我們自定義的Action
    class BCASTAction:public PluginASTAction{
    public:
        bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg){
            return true;
        }
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile){
            return unique_ptr<BCConsumer>(new BCConsumer);
        }
    };
}
//注冊插件 BCPlugin
static FrontendPluginRegistry::Add<BCPlugin::BCASTAction>
BC("BCPlugin","this is BCPlugin");
BCPlugindylib@2x.png
  • 3.編寫測試代碼hello.m,查看代碼中的頂級節點,在當前目錄下新建代碼文件夾下新建hello.m文件
int sum(int a);//頂級節點
int a;//頂級節點
int sum(int a){//頂級節點
  int b = 10;
  return a + b;
}

int sum2(int a,int b ){//頂級節點
    int c = 10;
    return a + b + c;
}

?  代碼 /Users/cloud/Documents/iOS/1113/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk/ -Xclang -load -Xclang /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib -Xclang -add-plugin -Xclang BCPlugin -c ./hello.m 
正在解析。。。。
正在解析。。。。
正在解析。。。。
正在解析。。。。
文件解析完畢!
hello.m@2x.png
  • 4.進行編譯,需要用到三個文件:自己編譯的clang文件,Xcode里面的模擬器sdk,自己新建的自定義插件編譯好的.dylib,編譯完成后在此hello.m目錄下會生成hello.o文件,源文件變成目標文件,機器能識別的文件,根據打印信息存在4個頂級節點
????????自己編譯的clang文件路徑???????? -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulat or12.2.sdk/ 
-Xclang -load -Xclang 
????插件(.dylib)路勁???? -Xclang -add-plugin
-Xclang ?????? 插件名 -c ???????? ????????源碼路徑

/Users/cloud/Documents/iOS/1113/build_xcode/Debug/bin/clang -isysroot 
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk/
-Xclang -load -Xclang /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib
-Xclang -add-plugin -Xclang BCPlugin -c ./hello.m
自定義插件編譯hello.m@2x.png
31.gif
hello.o@2x.png
  • 通過Xcode自帶系統的編譯器編譯ViewController.m,獲取抽象語法書節點
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m
ObjCPropertyDecl@2x.png
  • 5.獲取上下文所有節點數據并打印,打印了包含系統的所有節點數據
namespace BCPlugin {
    class BCMatchCallback:public MatchFinder::MatchCallback{
    public:
        void run(const::MatchFinder::MatchResult &Result){
           //通過result獲取到節點
         const ObjCPropertyDecl *propertyDecl =   Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
            if (propertyDecl) {
                string typeStr = propertyDecl->getType().getAsString();
                cout<<"------拿到了:"<<typeStr<<"-------"<<endl;
            }
        }
    };
    //自定義BCConsumer
    class BCConsumer:public ASTConsumer{
    private:
        //AST節點的查找過濾器
        MatchFinder matcher;
        BCMatchCallback callback;
    public:
        BCConsumer(){
            //添加一個MatchFinder去匹配objcPropertyDecl節點
            //回調在BCMatchCallback里面的run方法
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
        };
         //解析完一個頂級的聲明,就回調一次
         bool HandleTopLevelDecl(DeclGroupRef D){
            cout<<"正在解析。。。。"<<endl;
            return true;
         };
        
        //整個文件都解析完成的回調
         void HandleTranslationUnit(ASTContext &Ctx) {
             cout<<"文件解析完畢!"<<endl;
             matcher.matchAST(Ctx);
         }
    };

      //繼承PluginASTAction 實現我們自定義的Action
    class BCASTAction:public PluginASTAction{
    public:
        bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg){
            return true;
        }
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile){
            return unique_ptr<BCConsumer>(new BCConsumer);
        }
    };
}
//注冊插件 BCPlugin
static FrontendPluginRegistry::Add<BCPlugin::BCASTAction>
BC("BCPlugin","this is BCPlugin");

/Users/cloud/Documents/iOS/1113/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk/ -Xclang -load -Xclang /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib -Xclang -add-plugin -Xclang BCPlugin -c ./ViewController.m
獲取所有節點@2x.png
  • 6.獲取文件名稱
獲取文件名稱@2x.png
  • 7.過濾系統的節點,獲取自己的代碼節點
      bool isUserSourceCode(const string fileName){
            if (fileName.empty()) return false;
            //非Xcode中的源碼都認為是用戶的
            if (fileName.find("/Applications/Xcode.app/") == 0)
                return false;
            return true;
        }
獲取自己的代碼節點過濾系統的@2x.png
  • 8.NSString,NSArray,NSDictionary用copy修飾
    //判斷是否應該用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;
        }
提示必須copy修飾@2x.png
  • 9.定位到代碼錯誤
//拿到節點的描述信息
ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
//判斷應該使用copy但是沒有使用copy
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
            cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
}
定位到代碼錯誤@2x.png
  • 10.發出警告
//判斷應該使用copy但是沒有使用copy
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
}
//診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報告
diag.Report(diag.getCustomDiagID(DiagnosticsEngine::Warning, "這個地方推薦使用copy!!!"));
warning@2x.png
  • 11.確定發生錯誤的位置,并警告
                //判斷應該使用copy但是沒有使用copy
                if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
                    cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
                }
                //診斷引擎
                DiagnosticsEngine &diag = CI.getDiagnostics();
                //Report 報告
                diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "這個地方推薦使用copy!!!"));
確定警告位置@2x.png
  • 12.選定占位符,指出具體需要用copy修飾的位置
//判斷應該使用copy但是沒有使用copy
if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
}
//診斷引擎
DiagnosticsEngine &diag = CI.getDiagnostics();
//Report 報告
diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦使用copy!!!"))<<typeStr;
指定須用copy修飾的位置提示和警告@2x.png
  • 13.刪除cout代碼,集成到Xcode中,編譯后集成到Xcode中
  • 1.在Xcode -> Build Settings Other C Flags中輸入 -Xclang -load -Xclang 插件絕對路徑 -Xclang -add-plugin -Xclang 插件名稱(BCPlugin)
32.gif
  • 2.Cmd + B編譯報錯,由于clang插件需要使用對應的版本去加載,如果版本不一致則會導致編譯錯誤,出現如下錯誤,網上download下來的llvm和Xcode的clang版本不一定匹配,Xcode集成的clang和插件所依賴的clang不一定一致
clang編譯版本錯誤@2x.png

??????

error: unable to load plugin '/Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib': 'dlopen(/Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib, 9): Symbol not found: __ZN5clang12ast_matchers16objcPropertyDeclE
  Referenced from: /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib
  Expected in: flat namespace
 in /Users/cloud/Documents/iOS/1113/build_xcode/Debug/lib/BCPlugin.dylib'
warning: Could not read serialized diagnostics file: Cannot Load File: Failed to open diagnostics file (in target 'Demo' from project 'Demo')
Command CompileC failed with a nonzero exit code
  • ??在Build Settings欄目中新增兩項用戶自定義的設置CC和CXX,CC對應的是自己編譯的clang的絕對路徑,CXX對應的是自己編譯的clang++的絕對路徑,
    自己編譯的clang和cxx的絕對路徑在build_xcode/Debug/bin/路徑下,clang為二進制可執行文件,clang++文件為快捷方式
AddUserDefinedSetting@2x.png
33.gif
  • 3.Cmd + B繼續報錯,在Build settings中搜索index,將Enable index-While-Building Functionality的Default改為NO。
index錯誤@2x.png
indexDefault改為No@2x.png
  • 4.Cmd + B重新編譯,自定義插件生效


    自定義插件生效@2x.png
#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 BCPlugin {
    class BCMatchCallback:public MatchFinder::MatchCallback{
    private:
        CompilerInstance &CI;
        bool isUserSourceCode(const string fileName){
            if (fileName.empty()) return false;
            //非Xcode中的源碼都認為是用戶的
            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:
        BCMatchCallback(CompilerInstance &CI):CI(CI){}
        //真正的回調!
        void run(const::MatchFinder::MatchResult &Result){
           //通過result獲取到節點
         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();
                //拿到節點的描述信息
                ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes();
                //判斷應該使用copy但是沒有使用copy
                if (isShouldUseCopy(typeStr) && !(attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {
//                    cout<<typeStr<<"應該使用copy修飾!但是你沒有!"<<endl;
                }
                //診斷引擎
                DiagnosticsEngine &diag = CI.getDiagnostics();
                //Report 報告
                diag.Report(propertyDecl->getBeginLoc(),diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0這個地方推薦使用copy!!!"))<<typeStr;
//                cout<<"------拿到了:"<<typeStr<<"--屬于文件:"<<fileName<<endl;
            }
        }
    };
    //自定義BCConsumer
    class BCConsumer:public ASTConsumer{
    private:
        //AST節點的查找過濾器
        MatchFinder matcher;
        BCMatchCallback callback;
    public:
        BCConsumer(CompilerInstance &CI):callback(CI){
            //添加一個MatchFinder去匹配objcPropertyDecl節點
            //回調在BCMatchCallback里面的run方法
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
        };
         //解析完一個頂級的聲明,就回調一次
         bool HandleTopLevelDecl(DeclGroupRef D){
//            cout<<"正在解析。。。。"<<endl;
            return true;
         };
        
        //整個文件都解析完成的回調
         void HandleTranslationUnit(ASTContext &Ctx) {
//             cout<<"文件解析完畢!"<<endl;
             matcher.matchAST(Ctx);
         }
    };

      //繼承PluginASTAction 實現我們自定義的Action
    class BCASTAction:public PluginASTAction{
    public:
        bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg){
            return true;
        }
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile){
            return unique_ptr<BCConsumer>(new BCConsumer(CI));
        }
    };
}
//注冊插件 BCPlugin
static FrontendPluginRegistry::Add<BCPlugin::BCASTAction>
BC("BCPlugin","this is BCPlugin");

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

推薦閱讀更多精彩內容