-
上一节,我们熟悉了
LLVM
的完整流程
。
本节:
- 自定义
Clang插件
是实现Copy属性修饰符
的检测
和智能提示
。
(最终的使用场景
可能并没那么通用
,但主要目的
是彻底熟悉LLVM
)- 文末会附上
最终代码
和使用方法
。
- 配置LLVM环境
- 自定义插件
- 如果你也跟我一样
充满
着期待
。那我们就开始
吧。
1. 配置LLVM环境
⚠️ ⚠️ ⚠️ 【注意】
LLVM
源码2.29G
,编译后
文件将近30G
,请确保
电脑硬盘空间足够
;编译时
,电脑温度会飙升90多度
,CPU
资源占满
,请用空调伺候
着,可能
会黑屏
;编译时间
长达1个多小时
,请合理安排时间。
如果以上3点
,你确定能接受
,那我们就开始
吧。
1.1 LLVM下载
- 在github(👉 下载地址)下载
LLVM
相关资源库
:
clang
、clang-tools-extra
、compiler-rt
、libcxx
、libcxxabi
、llvm
五个库:(我下载的都是11.0.0版本的)
image.png
-
解压
并移除
名称中的版本号
image.png
- 按以下顺序将
文件夹
移到指定位置
:
- 将
clang-tools-extra
移到clang
文件夹中的clang/tools
文件中 - 将
clang
文件夹移到llvm/tools
中 - 将
compiler-rt
、libcxx
、libcxxabi
都移到llvm/projects
中
image.png
1.2 安装cmake
- 查看
brew列表
,检查是否安装过cmake
,如果有,就跳过此步骤
brew list
- 如果没有,就使用
brew安装
:
brew install cmake
- 如果报权限错误,可
sudo chown -R `whoami`:admin /usr/local/share
放开权限
image.png
1.3 编译
- 在
llvm
同级目录创建build
文件夹,cd
到build文件夹,运行cmake
命令,将llvm
编译成Xcode
项目
cd build
cmake -G Xcode ../llvm
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="Release" ../llvm
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="debug" ../llvm
注意:
build
文件夹是存放cmake
生成的Xcode文件
的。放哪里都可以。
cmake
编译的对象是llvm
文件。所以使用cmake -G Xcode ../llvm
编译并生成Xcode文件时,请核对llvm
的文件路径
。
-
成功之后,可以看到生成的
image.pngXcode
文件:
-
打开
LLVM.xcodeproj
选择
image.png手动创建Schemes
- 添加
clang
和clangTooling
两个Target,并完成
两个target的编译
(此处可能需要1小时
,cpu占满
,请适当给电脑降温
😂)
image.png
-
编译成功
后,我们的准备工作
就完成
了。可以正式开始插件开发
了
2. 自定义插件
2.1 添加插件
- 我们在
llvm/tools/clang/tools
文件夹中,创建HTPlugin
文件夹,并新增
两个文件:
image.png
CMakeLists.txt
内容:add_llvm_library( HTPlugin MODULE BUILDTREE_ONLY HTPlugin.cpp)
HTPlugin.cpp
:空文件
image.png
- 在
llvm/tools/clang/tools
文件夹的CMakeLists.txt
文件尾部,加上add_clang_subdirectory(HTPlugin)
- 创建好后,我们回到
build
文件夹,cmake -G Xcode ../llvm
重新编译
生成Xcode
文件。
开始编译
编译完成
ps: 我的
image.png电脑系统版本
是:
- 打开
build文件夹
中新生成的LLVM.xcodeproj
:
Command + 鼠标左键
,点击
文件夹,折叠所有文件夹。 在Loadable modules
中,可以看到我们自己创建的HTPlugin
文件夹:
image.png搜索
并选择 HTPlugin
:
image.png
2.2 书写插件
2.2.1 体验单文件
的顶级节点
解析
1. 在HTPlugn.cpp
文件中加入
以下代码
:
// 1. 声明命名空间,创建插件
namespace HTPlugin {
// 4. 自定义HTConsumer,继承ASTConsumer
// 进入`ASTConsumer`,查看它的结构,有很多选择
// 本例重载[HandleTopLevelDecl]顶级节点的解析和[HandleTranslationUnit]文件解析结束回调
class HTConsumer: public ASTConsumer {
private:
public:
// 4.1 顶级节点解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"解析中..."<<endl;
return true;
}
// 4.2 单个文件解析结束
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完毕!"<<endl;
}
};
// 2. 创建插件(继承PluginASTAction 实现自定义的ASTAction)
// 【目的】读取AST语法树的所有节点。
// 【如何重写】 进入`PluginASTAction`,查看它的结构,重载[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.1 解析成功,就返回true。 我们直接写true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.2 创建一个语法树对象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定义的HTConsumer,继承自ASTConsumer。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
return unique_ptr<HTConsumer>(new HTConsumer());
}
};
}
// 3. 注册插件 (参数1:插件名称, 参数2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
2. Command+B
编译一次
3. 创建一个测试文件demo.m
:
-
demo.m
内容:
int sum(int a);
int a = 10;
int b = 20;
int sum(int a) {
int b = 10;
return 10 + b;
}
int sum2(int a, int b) {
int c = 10;
return a + b + c;
}
4. cd
到ClangDemo
文件夹,使用我们的clang
编译demo.m
文件:
/Users/ht/Desktop/llvm/build/Debug/bin/clang -usysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk/ -Xclang -load -Xclang /Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib -Xclang -add-plugin -Xclang HTPlugin -c demo.m
image.png格式分析:
/Users/ht/Desktop/llvm/build/Debug/bin/clang
:
自己插件编译后
的clang
文件,在build/Debug/bin/
文件夹中:
image.png/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk/
:
使用自己本机
的SDK
绝对路径。 (我这是14.2)
image.png/Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib
:
自己插件编译后
的HTPlugin.dylib
插件动态库绝对路径
,在build/Debug/lib/
文件夹中:
image.png- 指定使用
HTPlugin
编译demo.m
文件
5. 编译完成后,会生成demo.o
文件。是一个object目标文件
。(不懂目标文件,可回顾上一节)
2.2.2 观察属性修饰符
节点名
1. 创建工程
-
在
image.pngClangDemo
文件夹中,创建demo
工程:
-
在
ViewController.m
文件中加入测试代码
:
@interface ViewController()
// NSString、NSArray、NSDictionary 应使用Copy修饰
@property (nonatomic, strong) NSString * name;
@property (nonatomic, strong) NSArray * arrs;
@property (nonatomic, strong) NSDictionary *dicts;
@end
2. 使用系统clang
编译ViewController.m
文件
-
cd
到ViewController.m
的文件夹,使用系统clang
编译ViewController.m
文件:
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m
image.png
- 可以发现在
AST语法树
中,ObjCPropertyDecl
是属性节点
。
2.2.3 过滤ObjCPropertyDecl
节点
- 修改
HTPlugin
文件:
#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;
// 1. 声明命名空间,创建插件
namespace HTPlugin {
// 5. 自定义HTMatchCallBack(过滤器回调类) 继承自MatchFinder的MatchCallBack
class HTMatchCallBack: public MatchFinder::MatchCallback {
public:
// 5.2 重写run方法
void run(const MatchFinder::MatchResult &Result) {
// 通过Result拿到了所有节点, 通过自定义的节点标识,拿到我们标记的所有节点
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
// 过滤空情况
if (propertyDecl) {
// 拿到类型,转string,打印
string typeStr = propertyDecl->getType().getAsString();
cout<<"--- 拿到了: 【"<<typeStr<<"】 ----"<<endl;
}
}
};
// 4. 自定义HTConsumer,继承ASTConsumer
// 进入`ASTConsumer`,查看它的结构,有很多选择
// 本例重载[HandleTopLevelDecl]顶级节点的解析和[HandleTranslationUnit]文件解析结束回调
class HTConsumer: public ASTConsumer {
private:
// 4.3 添加属性:AST节点查找过滤器
MatchFinder matcher;
// 5.1 添加属性:MatchFinder过滤器的回调函数
HTMatchCallBack callback;
public:
// 4.5 声明构造方法(出厂就添加一个MatchFinder过滤器)
HTConsumer() {
// 添加MatchFinder (参数1: 过滤的节点, 参数2, 过滤后的回调)
// 查看MatchFinder中的MatchCallBack的结构,仿写一个回调函数
// 此处,过滤`objcPropertyDecl`属性节点,手动添加节点标识`objcPropertyDecl`
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
// 4.1 顶级节点解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"解析中..."<<endl;
return true;
}
// 4.2 单个文件解析结束
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完毕!"<<endl;
// 4.4 解析完成后,将语法树给到`matcher`过滤器
matcher.matchAST(Ctx);
}
};
// 2. 创建插件(继承PluginASTAction 实现自定义的ASTAction)
// 【目的】读取AST语法树的所有节点。
// 【如何重写】 进入`PluginASTAction`,查看它的结构,重载[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.2 解析成功,就返回true。 我们直接写true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.3 创建一个语法树对象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定义的HTConsumer,继承自ASTConsumer。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
return unique_ptr<HTConsumer>(new HTConsumer());
}
};
}
// 3. 注册插件 (参数1:插件名称, 参数2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
-
Command+B
编译 -
cd
到llvm/ClangDemo/Demo/Demo
文件夹。 使用我们自己的clang
和HTPlugin
插件,编译ViewController.m
文件:
/Users/ht/Desktop/llvm/build/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk -Xclang -load -Xclang /Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib -Xclang -add-plugin -Xclang HTPlugin -c ViewController.m
分享我掉的一个坑:
- 这是我自己
作
,因为我之前之前写了MacOS
项目,所以创建Demo
时,默认创建了MacOS
工程,然后我使用了iPhoneSimulator14.2.sdk
的iOS SDK
来编译。一直报'Cocoa/Cocoa.h' file not found
image.png出现
Cocoa/Cocoa.h
的问题,都是MacOS
的问题,iOS使用Fundation
库。macOS使用Cocoa
库。
-
可以看到
image.png打印
了非常多的属性节点
信息:
-
其中
大部分
都是系统文件
的属性
,我们需要排除
这些,定位
到自己代码
的属性
。
2.2.4 过滤系统文件
节点
可以通过编译器实例
(文件)CompilerInstance,读取每个实例
的路径
。代码如下:(新增6)
// 1. 声明命名空间,创建插件
namespace HTPlugin {
// 5. 自定义HTMatchCallBack(过滤器回调类) 继承自MatchFinder的MatchCallBack
class HTMatchCallBack: public MatchFinder::MatchCallback {
private:
// 6.2 添加属性:编译器实例
CompilerInstance &compilerInstance;
public:
// 6.3 声明构造方法,入参新增CompilerInstance编译器实例,并赋值给compilerInstance
HTMatchCallBack(CompilerInstance &CI):compilerInstance(CI) { }
// 5.2 重写run方法 (回调的执行函数)
void run(const MatchFinder::MatchResult &Result) {
// 通过Result拿到了所有节点, 通过自定义的节点标识,拿到我们标记的所有节点
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
// 过滤空情况
if (propertyDecl) {
// 6.4 打印文件名称
string fileName = compilerInstance.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
// 拿到类型,转string,打印
string typeStr = propertyDecl->getType().getAsString();
cout<<"--- 拿到了: 【"<<typeStr<<"】 ----文件路径:"<<fileName<<endl;
}
}
};
// 4. 自定义HTConsumer,继承ASTConsumer
// 进入`ASTConsumer`,查看它的结构,有很多选择
// 本例重载[HandleTopLevelDecl]顶级节点的解析和[HandleTranslationUnit]文件解析结束回调
class HTConsumer: public ASTConsumer {
private:
// 4.3 添加属性:AST节点查找过滤器
MatchFinder matcher;
// 5.1 添加属性:MatchFinder过滤器的回调函数
HTMatchCallBack callback;
public:
// 4.5 声明构造方法(出厂就添加一个MatchFinder过滤器)
// 6.4 入参新增:编译器实例CI,并赋值给callback
HTConsumer(CompilerInstance &CI):callback(CI) {
// 添加MatchFinder (参数1: 过滤的节点, 参数2, 过滤后的回调)
// 查看MatchFinder中的MatchCallBack的结构,仿写一个回调函数
// 此处,过滤`objcPropertyDecl`属性节点,手动添加节点标识`objcPropertyDecl`
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
// 4.1 顶级节点解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"解析中..."<<endl;
return true;
}
// 4.2 单个文件解析结束
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完毕!"<<endl;
// 4.4 解析完成后,将语法树给到`matcher`过滤器
matcher.matchAST(Ctx);
}
};
// 2. 创建插件(继承PluginASTAction 实现自定义的ASTAction)
// 【目的】读取AST语法树的所有节点。
// 【如何重写】 进入`PluginASTAction`,查看它的结构,重载[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.2 解析成功,就返回true。 我们直接写true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.3 创建一个语法树对象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定义的HTConsumer,继承自ASTConsumer。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
// 6.1 存储CompilerInstance实例(它是每个编译文件,可以通过它读取编译文件的路径,剔除系统文件的干扰)
return unique_ptr<HTConsumer>(new HTConsumer(CI));
}
};
}
// 3. 注册插件 (参数1:插件名称, 参数2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
-
Command+B
编译后,使用自己的clang
和HTPlugin
编译ViewController.m
文件:
/Users/ht/Desktop/llvm/build/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk -Xclang -load -Xclang /Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib -Xclang -add-plugin -Xclang HTPlugin -c ViewController.m
-
可以发现,所有系统文件,地址都是以
image.png/Applications/Xcode.app/
开头。
-
通过区分
文件地址
,可以剔除
所有系统文件
:(新增7)
#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;
// 1. 声明命名空间,创建插件
namespace HTPlugin {
// 5. 自定义HTMatchCallBack(过滤器回调类) 继承自MatchFinder的MatchCallBack
class HTMatchCallBack: public MatchFinder::MatchCallback {
private:
// 6.2 添加属性:编译器实例
CompilerInstance &compilerInstance;
// 7. 判断是否是用户代码的函数(通过文件地址判断)
bool isUserSourceCode(const string fileName) {
// 空,不是用户的
if (fileName.empty()) return false;
// Xcode中的源码,都是系统的
if (fileName.find("/Applications/Xcode.app/") == 0) return false;
// 其他情况,都是用户的
return true;
}
public:
// 6.3 声明构造方法,入参新增CompilerInstance编译器实例,并赋值给compilerInstance
HTMatchCallBack(CompilerInstance &CI):compilerInstance(CI) { }
// 5.2 重写run方法 (回调的执行函数)
void run(const MatchFinder::MatchResult &Result) {
// 通过Result拿到了所有节点, 通过自定义的节点标识,拿到我们标记的所有节点
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
// 过滤空情况
if (propertyDecl) {
// 6.4 打印文件名称
string fileName = compilerInstance.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
// 7.1 不是用户的文件,就不往下打印
if (!isUserSourceCode(fileName)) return;
// 拿到类型,转string,打印
string typeStr = propertyDecl->getType().getAsString();
cout<<"--- 拿到了: 【"<<typeStr<<"】 ----文件路径:"<<fileName<<endl;
}
}
};
// 4. 自定义HTConsumer,继承ASTConsumer
// 进入`ASTConsumer`,查看它的结构,有很多选择
// 本例重载[HandleTopLevelDecl]顶级节点的解析和[HandleTranslationUnit]文件解析结束回调
class HTConsumer: public ASTConsumer {
private:
// 4.3 添加属性:AST节点查找过滤器
MatchFinder matcher;
// 5.1 添加属性:MatchFinder过滤器的回调函数
HTMatchCallBack callback;
public:
// 4.5 声明构造方法(出厂就添加一个MatchFinder过滤器)
// 6.4 入参新增:编译器实例CI,并赋值给callback
HTConsumer(CompilerInstance &CI):callback(CI) {
// 添加MatchFinder (参数1: 过滤的节点, 参数2, 过滤后的回调)
// 查看MatchFinder中的MatchCallBack的结构,仿写一个回调函数
// 此处,过滤`objcPropertyDecl`属性节点,手动添加节点标识`objcPropertyDecl`
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
// 4.1 顶级节点解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
cout<<"解析中..."<<endl;
return true;
}
// 4.2 单个文件解析结束
void HandleTranslationUnit(ASTContext &Ctx) {
cout<<"文件解析完毕!"<<endl;
// 4.4 解析完成后,将语法树给到`matcher`过滤器
matcher.matchAST(Ctx);
}
};
// 2. 创建插件(继承PluginASTAction 实现自定义的ASTAction)
// 【目的】读取AST语法树的所有节点。
// 【如何重写】 进入`PluginASTAction`,查看它的结构,重载[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.2 解析成功,就返回true。 我们直接写true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.3 创建一个语法树对象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定义的HTConsumer,继承自ASTConsumer。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
// 6.1 存储CompilerInstance实例(它是每个编译文件,可以通过它读取编译文件的路径,剔除系统文件的干扰)
return unique_ptr<HTConsumer>(new HTConsumer(CI));
}
};
}
// 3. 注册插件 (参数1:插件名称, 参数2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
-
Command+B
编译后,使用自己的clang
和HTPlugin
编译ViewController.m
文件:
- 可以发现,我们已
成功
过滤系统消息
,只留下我们自己的代码信息。
2.2.5 定位
代码错误
,添加错误提示
- 添加
错误修饰符
的定位
和提示
的代码:(新增8)
#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;
// 1. 声明命名空间,创建插件
namespace HTPlugin {
// 5. 自定义HTMatchCallBack(过滤器回调类) 继承自MatchFinder的MatchCallBack
class HTMatchCallBack: public MatchFinder::MatchCallback {
private:
// 6.2 添加属性:编译器实例
CompilerInstance &compilerInstance;
// 7. 判断是否是用户代码的函数(通过文件地址判断)
bool isUserSourceCode(const string fileName) {
// 空,不是用户的
if (fileName.empty()) return false;
// Xcode中的源码,都是系统的
if (fileName.find("/Applications/Xcode.app/") == 0) return false;
// 其他情况,都是用户的
return true;
}
// 8. 判断是否应该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:
// 6.3 声明构造方法,入参新增CompilerInstance编译器实例,并赋值给compilerInstance
HTMatchCallBack(CompilerInstance &CI):compilerInstance(CI) { }
// 5.2 重写run方法 (回调的执行函数)
void run(const MatchFinder::MatchResult &Result) {
// 通过Result拿到了所有节点, 通过自定义的节点标识,拿到我们标记的所有节点
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
// 过滤空情况
if (propertyDecl) {
// 6.4 打印文件名称
string fileName = compilerInstance.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
// 7.1 不是用户的文件,就不往下打印
if (!isUserSourceCode(fileName)) return;
// 拿到类型,转string,打印
string typeStr = propertyDecl->getType().getAsString();
// 8.1 找出应该使用copy的属性
if (isShouldUseCopy(typeStr)) {
// 拿到节点描述 (ObjCPropertyAttribute::Kind 是枚举,其中kind_copy = 0x20)
ObjCPropertyAttribute::Kind attrKind = propertyDecl->getPropertyAttributes();
// 当前修饰符不为copy,提示
if (!(attrKind & ObjCPropertyAttribute::kind_copy)) {
// 诊断引擎
DiagnosticsEngine &diag = compilerInstance.getDiagnostics();
// Report 报告
// 参数1: 定位位置
// 参数2: 设置等级和提示文案
diag.Report(propertyDecl->getBeginLoc(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0这个地方推荐使用copy!!!"))<<typeStr;
}
}
}
}
};
// 4. 自定义HTConsumer,继承ASTConsumer
// 进入`ASTConsumer`,查看它的结构,有很多选择
// 本例重载[HandleTopLevelDecl]顶级节点的解析和[HandleTranslationUnit]文件解析结束回调
class HTConsumer: public ASTConsumer {
private:
// 4.3 添加属性:AST节点查找过滤器
MatchFinder matcher;
// 5.1 添加属性:MatchFinder过滤器的回调函数
HTMatchCallBack callback;
public:
// 4.5 声明构造方法(出厂就添加一个MatchFinder过滤器)
// 6.4 入参新增:编译器实例CI,并赋值给callback
HTConsumer(CompilerInstance &CI):callback(CI) {
// 添加MatchFinder (参数1: 过滤的节点, 参数2, 过滤后的回调)
// 查看MatchFinder中的MatchCallBack的结构,仿写一个回调函数
// 此处,过滤`objcPropertyDecl`属性节点,手动添加节点标识`objcPropertyDecl`
matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
// 4.1 顶级节点解析中
bool HandleTopLevelDecl(DeclGroupRef D) {
// cout<<"解析中..."<<endl;
return true;
}
// 4.2 单个文件解析结束
void HandleTranslationUnit(ASTContext &Ctx) {
// cout<<"文件解析完毕!"<<endl;
// 4.4 解析完成后,将语法树给到`matcher`过滤器
matcher.matchAST(Ctx);
}
};
// 2. 创建插件(继承PluginASTAction 实现自定义的ASTAction)
// 【目的】读取AST语法树的所有节点。
// 【如何重写】 进入`PluginASTAction`,查看它的结构,重载[ParseArgs]和[CreateASTConsumer]
class HTASTAction: public PluginASTAction {
public:
// 2.2 解析成功,就返回true。 我们直接写true
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &arg) {
return true;
}
// 2.3 创建一个语法树对象
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile){
// 使用自定义的HTConsumer,继承自ASTConsumer。
// return unique_ptr<ASTConsumer>(new ASTConsumer());
// 6.1 存储CompilerInstance实例(它是每个编译文件,可以通过它读取编译文件的路径,剔除系统文件的干扰)
return unique_ptr<HTConsumer>(new HTConsumer(CI));
}
};
}
// 3. 注册插件 (参数1:插件名称, 参数2:插件描述)
static FrontendPluginRegistry::Add<HTPlugin::HTASTAction> HT("HTPlugin", "this is HTPlugin");
-
Command+B
编译后,使用自己的clang
和HTPlugin
编译ViewController.m
文件:
image.png
2.2.6 Xcode集成
自定义插件
1. Demo
工程添加自定义插件 Build Settings
->Other C Flags
添加:
-Xclang -load -Xclang /Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib -Xclang -add-plugin -Xclang HTPlugin
image.png
/Users/ht/Desktop/llvm/build/Debug/lib/HTPlugin.dylib
是自己的HTPlugin.dylib
绝对路径
2. Command + B
编译,报错:
-
image.pngClang插件
需要使用对应的版本
去加载
,版本不一致
导致的编译错误
:
-
在
image.pngBuild Settings
栏目新增两项
用户定义的设置:
-
添加
CC
和CXX
两个设置:
image.png
CC
: 填写clang
绝对路径
CXX
:填写clang++
绝对路径
image.png
- 在
Build Settings
搜索index
:
image.png
2.2.7 编译成功
Command+B
编译,编译成功
,查看ViewController.m
文件:
-
修改
image.pngname
的修饰符为copy
,Command+B
编译后看,name
已经不报错
了。
-
恭喜你。 成功了!
image.png
通过这个小插件
,应该对语法树
、编译流程
,有了更深刻
的认识
。😃
网友评论