iOS 底层探索(二十四)Clang插件
程序员文章站
2024-03-24 21:48:10
...
iOS 底层探索(二十四)Clang插件
在LLVM文章中编译了llvm
创建插件
- 在
/clang/tools
目录下新建插件CustomPlugin
文件夹
- 修改
/clang/tools
目录下的CMakeLists.txt
文件,新增add_clang_subdirectory(CustomPlugin)
.
- 在
CustomPlugin
目录下新建一个名为CustomPlugin.cpp
的文件和CMakeLists.txt
的文件。在CMakeLists.txt
中写上如下代码
add_llvm_library(CustomPlugin MODULE BUILDTREE_ONLY
CustomPlugin.cpp
)
- 接下来利用
cmake
重新生成一下Xcode项目,在build_xcode
目录中执行cmake -G Xcode ../llvm
- 最后可以在LLVM的Xcode项目中可以看到
Loadable modules
目录下有自己的CustomPlugin
目录了,我们可以在里面编写插件代码。
编写插件代码
我们编写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");
我们使用自定义的clang
即build_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(源码路径)
运行结果如下
从运行结果可知,上方的*节点一共有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
运行结果如下
我们只关心-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
运行结果如下
至此,Clang
的插件已经开发完了。
Xcode 集成插件
加载插件
打开测试项目,在Build Settings
-> Other C Flags
添加上如下内容:
-Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang CustomPlugin
设置编译器
- 由于
Clang
插件需要使用对应的版本去加载,如果版本不一致,则会导致编译错误,会出现如下图所示错误。
- 在
Build Settings
栏目中新增两项用户定义的设置
- 分别是
CC
和CXX
-
CC
对应的是自己编译的clang
的绝对路径 -
CXX
对应的是自己编译的clang++
的绝对路径
-
- 接下来在
Build Settings
栏目中搜索index
,将Enable Index-Wihle-Building Functionality
改为NO
- 重新编译查看结果
- 至此
Clang
插件自定义完成。
下一篇: xshell使用