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阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容