Clang 插件能够控制编译过程,可以加 warning,或者直接中断编译提示错误。本文将讲述如何使用 Xcode 开发 Clang 插件,Clang 插件可以在编译期间运行额外的用户定义操作。
Clang 插件代码编写后进行编译的前置条件是编译 Clang,首先我们需要准备好 llvm-project 工程,步骤可以查看了解 LLVM 编译器
准备编写 Clang 插件
-
编写之前,先在 llvm-project/clang/tools/ 目录下创建 Clang 插件的目录
DemoPlugin,添加 DemoPlugin.cpp 文件和 CMakeLists.txt 文件。其中,CMake编译需要通过 CMakeLists.txt 文件来指导编译,cpp 是源文件。
DemoPlugin
-
使用如下代码编写 DemoPlugin/CMakeLists.txt 文件,来定制编译流程:
add_clang_library(DemoPlugin MODULE DemoPlugin.cpp)这段代码是指,要将 Clang 插件代码集成到 LLVM 的 Xcode 工程中,并作为一个模块进行编写调试。
-
添加插件
编写 llvm-project/clang/tools/CMakeLists.txt,在最下面一行添加add_clang_subdirectory(DemoPlugin) -
在 llvm-project/build_xcode 下编译
cd build_xcode cmake -D CMAKE_C_COMPILER=/usr/bin/gcc -D CMAKE_CXX_COMPILER=/usr/bin/g++ -G Xcode ../llvm -DLLVM_ENABLE_PROJECTS="clang"
注意:改变 CMakeLists.txt,之后需要 cmake 重新编译
插件开发
编写 Clang 插件代码,入口就是 FrontActions。接下来,我们就一起看看 FrontActions 是什么?
FrontAction 是什么?
FrontActions 是编写 Clang 插件的入口,也是一个接口,是基于 ASTFrontendAction 的抽象基类。其实,FrontActions 并没干什么实际的事情,只是为接下来基于 AST 操作的函数提供了一个入口和工作环境。通过这个接口,可以编写要在编译过程中自定义的操作,具体方式是:通过 ASTFrontendAction 在 AST 上自定义操作,重载 CreateASTConsumer 函数返回自定义的 Consumer,以获取 AST 上的 ASTConsumer 单元。
写一个 PluginASTAction
在我们开发 Clang 插件的时候,由于 Clang 插件是没有 main 函数的,入口是 PluginASTAction 的 ParseArgs 函数。所以,编写 Clang 插件需要实现 ParseArgs 来处理入口参数。当然了,在实现之前我们需要先引入头文件#include "clang/Frontend/FrontendPluginRegistry.h"。代码如下所示:
#include <iostream>
#include "clang/Frontend/FrontendPluginRegistry.h"
using namespace clang;
using namespace std;
namespace DemoPlugin{
//自定义 ASTAction
class DemoPluginASTAction:public PluginASTAction {
public:
//需要实现的方法
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) override{
return true;
}
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override{
return unique_ptr<ASTConsumer>(new DemoPluginConsumer());
}
};
}
实现自定义的 ASTConsumer
ASTConsumer 可以提供很多入口,是一个可以访问 AST 的抽象基类,可以重载 HandleTopLevelDecl() 和 HandleTranslationUnit() 两个函数,以接收访问 AST 时的回调。其中,HandleTopLevelDecl() 函数是在访问到全局变量、函数定义这样最上层声明时进行回调,HandleTranslationUnit() 函数会在接收每个节点访问时的回调。
//自定义 Consumer
class DemoPluginConsumer: public ASTConsumer{
public:
DemoPluginConsumer(){
cout<<"开始解析..."<<endl;
}
// clang 解析完顶级的声明的回调
bool HandleTopLevelDecl(DeclGroupRef D) override{
return true;
}
//整个文件都解析完成的回调
void HandleTranslationUnit(ASTContext &Ctx) override{
cout<<"文件解析完毕..."<<endl;
}
};
注册插件
插件在运行时由编译器从动态库中加载。要在库中注册插件,需要使用 FrontendPluginRegistry::Add<>:
static FrontendPluginRegistry::Add<DemoPlugin::DemoPluginASTAction> X("DemoPlugin","This is plugin description");
编译插件
选择名为 DemoPlugin 的 Scheme,使用 Xcode 的 Debug(Command+B),编译生成的插件在 llvm-project/build_xcode/Debug/lib/DemoPlugin.dylib
选择名为 clang 的 Scheme,使用 Xcode 的 Debug(Command+B),编译生成的 clang 在 llvm-project/build_xcode/Debug/bin/clang
调试插件
当我们编译好插件后,可以使用如下示例终端命令进行,其中 clang 需要 Debug 生成的 clang 路径,避免版本不一致。
~/llvm-project/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator15.2.sdk/ -Xclang -load -Xclang ~/llvm-project/build_xcode/Debug/lib/DemoPlugin.dylib -Xclang -add-plugin -Xclang DemoPlugin -c ./ViewController.m
Xcode 自定义插件
在 Target-Build Settings-Other C Flags 中填写如下面示例的指令,其中
~/llvm-project/build_xcode/Debug/lib/DemoPlugin.dylib 是插件的路径,DemoPlugin 是插件的名称。
-Xclang -load -Xclang ~/llvm-project/build_xcode/Debug/lib/DemoPlugin.dylib -Xclang -add-plugin -Xclang DemoPlugin
小结
利用 Clang 的分析能力,可以在它对代码 Clang AST 分析过程中,获取到 AST 各个节点的信息。获取到源码全量信息后,就可以更加精准的分析源码,然后统计出不满足编码规范的地方。
同时,访问 SourceManager 和 ASTContext,还能够获取到节点所在源代码中的位置信息。这样的话,我们就可以直接通过 Clang 插件,在问题节点原地修改不规范的代码。我们可以在 CreateASTConsumer 期间从 CompilerInstance 中获取 ASTContext,进而使用其中的 SourceManager 里的 getFullLoc 方法,来获取 AST 节点所在源码的位置。
最后,可以通过Clang插件官方示例 以及 ASTMatchersReference,了解更多 Clang 插件 和 AST 结点的信息。














网友评论