欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS 底层探索(二十四)Clang插件

程序员文章站 2024-03-24 21:48:10
...

iOS 底层探索(二十四)Clang插件

LLVM文章中编译了llvm

创建插件

  • /clang/tools目录下新建插件CustomPlugin文件夹

iOS 底层探索(二十四)Clang插件

  • 修改/clang/tools目录下的CMakeLists.txt文件,新增add_clang_subdirectory(CustomPlugin).

iOS 底层探索(二十四)Clang插件

  • CustomPlugin目录下新建一个名为CustomPlugin.cpp的文件和CMakeLists.txt的文件。在CMakeLists.txt中写上如下代码
add_llvm_library(CustomPlugin MODULE BUILDTREE_ONLY 
    CustomPlugin.cpp
)

iOS 底层探索(二十四)Clang插件

  • 接下来利用cmake重新生成一下Xcode项目,在build_xcode目录中执行cmake -G Xcode ../llvm
  • 最后可以在LLVM的Xcode项目中可以看到Loadable modules目录下有自己的CustomPlugin目录了,我们可以在里面编写插件代码。
    iOS 底层探索(二十四)Clang插件

编写插件代码

我们编写Clang插件,就是要重载Clang编译过程函数。因为Clang插件的目的就是读到某些代码,然后做某些事情。因此Clang怎么读代码不需要我们关心,我们只关心Clang读到代码后我们要做什么。

#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 CustomPlugin {

    // 自定义ASTConsumer
    class CustomConsumer:public ASTConsumer {
    public:
        // 解析完一个*节点就回调
        bool HandleTopLevelDecl(DeclGroupRef D) {
            cout<<"正在解析..."<<endl;
            return true;
        }
        
        void HandleTranslationUnit(ASTContext &Ctx) {
            cout<<"文件解析完毕!!!"<<endl;
        }
    };

    // 继承PluginASTAction 实现我们自定义的Action
    class CustomASTAction:public PluginASTAction {
        // 读取抽象语法树的节点
    public:
        bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) {
            return true;
        }
        
        std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
            return unique_ptr<CustomConsumer>(new CustomConsumer());
        }
    };

    
}

// 注册插件
static FrontendPluginRegistry::Add<CustomPlugin::CustomASTAction> Custom("CustomPlugin", "This is the description of the plugin");

我们使用自定义的clangbuild_xcode目录下的clang.
自定义一个C语言代码。如下

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

运行命令为

$ /Users/ued/Desktop/llvm-project/build_xcode/Debug/bin/clang(自己编译的clang文件路径) -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk(Xcode模拟器SDK路径) -Xclang -load -Xclang /Users/ued/Desktop/llvm-project/build_xcode/Debug/lib/CustomPlugin.dylib(自己编译的插件(.dylib)路径) -Xclang -add-plugin -Xclang CustomPlugin(插件名) -c ./main.m(源码路径)

运行结果如下
iOS 底层探索(二十四)Clang插件

从运行结果可知,上方的*节点一共有4个。那么*节点类似于全局变量。

解析代码

自定义iOS的代码,创建属性,代码如下

@interface ViewController ()

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

@end

在上面的属性定义中我们知道,应该使用copy修饰更好一点。先查看抽象语法树,命令如下

$ clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk(SDK路径) -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m

运行结果如下
iOS 底层探索(二十四)Clang插件

我们只关心-ObjCPropertyDecl这个节点。因此我们只监听该节点,并且过滤掉其他节点。
插件代码如下:

#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 CustomPlugin {

    // 自定义回调
    class CustomMatchCallback: 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 typeString) {
            if (typeString.find("NSString") != string::npos || typeString.find("NSArray") != string::npos || typeString.find("NSDictionary") != string::npos) {
                return true;
            }
            return false;
        }
    public:
        CustomMatchCallback(CompilerInstance &CI):CI(CI){};
        // 拿到objcPropertyDecl节点,就会执行这个方法。
        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 typeString = propertyDecl->getType().getAsString();
                // 拿到节点的描述信息
                ObjCPropertyAttribute::Kind attributeKind = propertyDecl->getPropertyAttributes();
                // 判断应该使用copy但是没有使用copy
                if (isShouldUseCopy(typeString) && !(attributeKind & ObjCPropertyAttribute::kind_copy)) {
                    // 报警告,因为这不是错误
                    DiagnosticsEngine &diag = CI.getDiagnostics();
                    diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "----建议使用 copy 修饰-----"))<<typeString;
//                    cout<<typeString<<"应该用copy修饰!但是你没有"<<endl;
                }
//                cout<<"---拿到了:"<<typeString<<"属于"<<fileName<<endl;
            }
        }
    };

    // 自定义ASTConsumer
    class CustomConsumer:public ASTConsumer {
    private:
        // AST节点的查找过滤器
        MatchFinder matcher;
        // 定义回调
        CustomMatchCallback callback;
    public:
        // 构造方法
        CustomConsumer(CompilerInstance &CI):callback(CI) {
            // 添加一个MatchFinder,匹配objcPropertyDecl节点
            // bind为给该节点绑定标识,当获取时需要根据该标记取节点
            // 回调在CustomMatchCallback 的run方法中
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
        }
        
        // 解析完一个*节点就回调
        bool HandleTopLevelDecl(DeclGroupRef D) {
            cout<<"正在解析..."<<endl;
            return true;
        }
        
        // 整个文件都解析完成的回调
        void HandleTranslationUnit(ASTContext &Ctx) {
            cout<<"文件解析完毕!!!"<<endl;
            // 给matcher传递语法树
            matcher.matchAST(Ctx);
        }
    };

    // 继承PluginASTAction 实现我们自定义的Action
    class CustomASTAction:public PluginASTAction {
        // 读取抽象语法树的节点
    public:
        bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) {
            return true;
        }
        
        std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
            //
            return unique_ptr<CustomConsumer>(new CustomConsumer(CI));
        }
    };
}

// 注册插件
static FrontendPluginRegistry::Add<CustomPlugin::CustomASTAction> Custom("CustomPlugin", "This is the description of the plugin");

其中Kind是一个枚举值

enum Kind {
  kind_noattr = 0x00,
  kind_readonly = 0x01,
  kind_getter = 0x02,
  kind_assign = 0x04,
  kind_readwrite = 0x08,
  kind_retain = 0x10,
  kind_copy = 0x20,
  kind_nonatomic = 0x40,
  kind_setter = 0x80,
  kind_atomic = 0x100,
  kind_weak = 0x200,
  kind_strong = 0x400,
  kind_unsafe_unretained = 0x800,
  /// Indicates that the nullability of the type was spelled with a
  /// property attribute rather than a type qualifier.
  kind_nullability = 0x1000,
  kind_null_resettable = 0x2000,
  kind_class = 0x4000,
  kind_direct = 0x8000,
  // Adding a property should change NumObjCPropertyAttrsBits
  // Also, don't forget to update the Clang C API at CXObjCPropertyAttrKind and
  // clang_Cursor_getObjCPropertyAttributes.
};

运行查看结果,运行命令如下

$ /Users/ued/Desktop/llvm-project/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.1.sdk -Xclang -load -Xclang /Users/ued/Desktop/llvm-project/build_xcode/Debug/lib/CustomPlugin.dylib -Xclang -add-plugin -Xclang CustomPlugin -c ./ViewController.m

运行结果如下
iOS 底层探索(二十四)Clang插件

至此,Clang的插件已经开发完了。

Xcode 集成插件

加载插件

打开测试项目,在Build Settings -> Other C Flags添加上如下内容:

-Xclang	-load	-Xclang	(.dylib)动态库路径 -Xclang -add-plugin -Xclang	CustomPlugin

iOS 底层探索(二十四)Clang插件

设置编译器

  • 由于Clang插件需要使用对应的版本去加载,如果版本不一致,则会导致编译错误,会出现如下图所示错误。

iOS 底层探索(二十四)Clang插件

  • Build Settings栏目中新增两项用户定义的设置

iOS 底层探索(二十四)Clang插件

  • 分别是CCCXX
    • CC对应的是自己编译的clang的绝对路径
    • CXX对应的是自己编译的clang++的绝对路径

iOS 底层探索(二十四)Clang插件

iOS 底层探索(二十四)Clang插件

  • 接下来在Build Settings栏目中搜索index,将Enable Index-Wihle-Building Functionality改为NO

iOS 底层探索(二十四)Clang插件

  • 重新编译查看结果

iOS 底层探索(二十四)Clang插件

  • 至此Clang插件自定义完成。