一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
分析OC
代码
搭建App
项目,打开ViewController.m
文件,写入以下代码:
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) NSString* name;
@property(nonatomic, strong) NSArray* arrs;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
@end
复制代码
生成AST
代码,找到属性的声明
- 在
ObjCPropertyDecl
节点中,可以找到属性的声明,包含属性的类型和修饰符
AST
节点的过滤
系统API
提供MatchFinder
,用于AST
语法树节点的查找
其中addMatcher函数,可以查找指定节点
void addMatcher(const DeclarationMatcher &NodeMatch,
MatchCallback *Action);
复制代码
参数1
:设置指定节点参数2
:执行回调,此处并非使用回调函数,而是一个回调类。需要继承MatchCallback
系统类,实现自己的子类
添加MatchFinder所在命名空间\
using namespace clang::ast_matchers;
复制代码
实现HKMatchHandler
回调类,继承自MatchCallback
class HKMatchHandler:public MatchFinder::MatchCallback{
public:
void run(const MatchFinder::MatchResult &Result) {
const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
if(propertyDecl){
string typeStr = propertyDecl->getType().getAsString();
cout<<"------拿到了:"<<typeStr<<endl;
}
}
};
复制代码
- 必须实现
run
函数,它就是真正的回调函数 - 通过
Result
结果,获取节点对象 - 通过节点对象的
getType().getAsString()
,以字符串的形式返回属性类型
在HKConsumer
类中,定义私有MatchFinder
和HKMatchHandler
,重写构造方法,添加AST
节点过滤器
class HKConsumer:public ASTConsumer{
private:
MatchFinder matcher;
HKMatchHandler handler;
public:
HKConsumer(){
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &handler);
}
};
复制代码
- 解析语法树,查找
objcPropertyDecl
节点
在文件解析完成的回调函数中,调用matcher
的matchAST
函数,将文件的语法树传入过滤器
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完成..."<<endl;
matcher.matchAST(Ctx);
}
复制代码
测试插件
- 通过语法树分析,可以找到属性的声明,包含属性的类型和修饰符
- 但也存在一些问题,在预处理阶段,头文件会被展开,我们可能会获取到系统头文件中的属性,所以我们要想办法过滤掉系统文件中的代码
过滤系统文件
可以通过文件路径判断系统文件,因为系统文件都存在于/Applications/Xcode.app/
开头的目录中
在PluginASTAction
类中,存在CompilerInstance
类型的CI
参数
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile) override = 0;
复制代码
CI
为编译器实例对象,可以通过它获取到文件路径,以及警告的提示
重写HKConsumer
的构造函数,增加CI
参数
HKConsumer(CompilerInstance &CI){
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &handler);
}
复制代码
在HKASTAction
类中,创建ASTConsumer
时,将CI
传入
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
return unique_ptr<HKConsumer> (new HKConsumer(CI));
}
复制代码
重写HKMatchHandler
的构造函数,增加CI
参数。定义私有CompilerInstance
,通过构造函数对其赋值
class HKMatchHandler:public MatchFinder::MatchCallback{
private:
CompilerInstance &CI;
public:
HKMatchHandler(CompilerInstance &CI):CI(CI){
}
};
复制代码
在HKConsumer
的构造函数中,对HKMatchHandler
中的CI
进行传递
HKConsumer(CompilerInstance &CI):handler(CI){
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &handler);
}
复制代码
在HKMatchHandler使用CI,获取文件路径并进行过滤\
class HKMatchHandler:public MatchFinder::MatchCallback{
private:
CompilerInstance &CI;
bool isUserSourceCode(const string fileName){
if(fileName.empty()){
return false;
}
if(fileName.find("/Applications/Xcode.app/")==0){
return false;
}
return true;
}
public:
HKMatchHandler(CompilerInstance &CI):CI(CI){
}
void run(const MatchFinder::MatchResult &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();
cout<<"------拿到了:"<<typeStr<<endl;
}
}
};
复制代码
- 通过
CI.getSourceManager().getFilename
获取文件名称,包含文件路径 - 需要传入
SourceLocation
,可以通过节点的propertyDecl->getSourceRange().getBegin()
获得 - 实现
isUserSourceCode
函数,判断路径非空,并且非/Applications/Xcode.app/
目录开头,视为自定义文件
测试插件
文件解析完成...
------拿到了:NSString *
------拿到了:NSArray *
复制代码
- 成功过滤系统文件,获取到自定义文件中的两个属性