iOS之LLVM 七

一起养成写作习惯!这是我参与「掘金日新计划 · 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代码,找到属性的声明

image-20.png

  • 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类中,定义私有MatchFinderHKMatchHandler,重写构造方法,添加AST节点过滤器

class HKConsumer:public ASTConsumer{
    
    private:
        MatchFinder matcher;
        HKMatchHandler handler;
    
    public:
    
        HKConsumer(){
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &handler);
        }
};
复制代码
  • 解析语法树,查找objcPropertyDecl节点

在文件解析完成的回调函数中,调用matchermatchAST函数,将文件的语法树传入过滤器

void HandleTranslationUnit(ASTContext &Ctx) {
    cout<<"文件解析完成..."<<endl;
    matcher.matchAST(Ctx);
}
复制代码

测试插件

image-21.png

  • 通过语法树分析,可以找到属性的声明,包含属性的类型和修饰符
  • 但也存在一些问题,在预处理阶段,头文件会被展开,我们可能会获取到系统头文件中的属性,所以我们要想办法过滤掉系统文件中的代码
过滤系统文件

可以通过文件路径判断系统文件,因为系统文件都存在于/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 *
复制代码
  • 成功过滤系统文件,获取到自定义文件中的两个属性

猜你喜欢

转载自juejin.im/post/7086068992799948836