LLVM Clang编译器编写的Xcode进行代码类名格式检验插件
一.源码
#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;
namespace WSHPlugin {
class MJHandler : public MatchFinder::MatchCallback {
private:
CompilerInstance &ci;
public:
MJHandler(CompilerInstance &ci) :ci(ci) {}
void run(const MatchFinder::MatchResult &Result) {
if (const ObjCInterfaceDecl *decl = Result.Nodes.getNodeAs<ObjCInterfaceDecl>("ObjCInterfaceDecl")) {
size_t pos = decl->getName().find('_');
if (pos != StringRef::npos) {
DiagnosticsEngine &D = ci.getDiagnostics();
SourceLocation loc = decl->getLocation().getLocWithOffset(pos);
D.Report(loc, D.getCustomDiagID(DiagnosticsEngine::Error, "温馨提示:类名中不能带有下划线"));
}
}
}
};
class MJASTConsumer: public ASTConsumer {
private:
MatchFinder matcher;
MJHandler handler;
public:
MJASTConsumer(CompilerInstance &ci) :handler(ci) {
matcher.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler);
}
void HandleTranslationUnit(ASTContext &context) {
matcher.matchAST(context);
}
};
class MJASTAction: public PluginASTAction {
public:
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
return unique_ptr<MJASTConsumer> (new MJASTConsumer(ci));
}
bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) {
return true;
}
};
}
static FrontendPluginRegistry::Add<WSHPlugin::MJASTAction>
X("WSHPlugin", "The WSHPlugin is my first clang-plugin.");
二.源代码分析
因为,LLVM是C++开发环境,因此代码都是通过C++来编写的。
1.导入头文件
#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
导入头文件Clang头文件,Clang是C,C++,Objective-C语言的前端开发编辑器。在Clang命令中我们可以看见程序变异中间有个缓解是通过生成树阶段,在程序编译生成树阶段,我们进行插件的书写。因此,导入除了iostream的C++头文件外,我们还需要导入AST生成树的头文件。
2.命名空间
using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;
命名空间使用,是用来简化C++代码可以直接名命名空间内的成员和方法。
namespace WSHPlugin {...}
3.注册一个插件
static FrontendPluginRegistry::Add<WSHPlugin::MJASTAction>
X("WSHPlugin", "The WSHPlugin is my first clang-plugin.");
(1)设置一个static静态方法,说明当调用这段代码语句时候,内存只加载一次。FrontendPluginRegistry(Frontend Plugin Registry – 前端插件注册);
(2)调用FrontendPluginRegistry的Add方法,把WSHPlugin命名空间下的MJASAction放入参数–表示一个生成树的行为。传入的参数是“框架的名称”–"WSHPlugin"和“框架的描述” – “The WSHPlugin is my first clang-plugin.”;
(3)Add<…> 后面说明是函数模板,根据模板传入参数;
4.创建一个Handler类 – 管理者类
class MJHandler : public MatchFinder::MatchCallback {
private:
CompilerInstance &ci;
public:
MJHandler(CompilerInstance &ci) :ci(ci) {}
void run(const MatchFinder::MatchResult &Result) {
if (const ObjCInterfaceDecl *decl = Result.Nodes.getNodeAs<ObjCInterfaceDecl>("ObjCInterfaceDecl")) {
size_t pos = decl->getName().find('_');
if (pos != StringRef::npos) {
DiagnosticsEngine &D = ci.getDiagnostics();
SourceLocation loc = decl->getLocation().getLocWithOffset(pos);
D.Report(loc, D.getCustomDiagID(DiagnosticsEngine::Error, "温馨提示:类名中不能带有下划线"));
}
}
}
};
(1)MJHandler继承MatchFinder下的MatchCallback回掉类;
(2)定义一个完成编译的实例对象ci;
(3)创建一个类的构造器,但是什么都不做;
(4)定义一个运行时候的方法,传入的参数是const类型(防止被修改,更加安全)MatchFinder::MatchResult的结果对象;
(5) 判断条件
if (const ObjCInterfaceDecl *decl = Result.Nodes.getNodeAs<ObjCInterfaceDecl>("ObjCInterfaceDecl")) {
size_t pos = decl->getName().find('_');
if (pos != StringRef::npos) {
...
}
}
(6) Diagnostics – 诊断 && DiagnosticsEngine – 诊断引擎【通过完成编译实例的对象ci获得诊断信息】;
DiagnosticsEngine &D = ci.getDiagnostics();
(7) SourceLocation loc – 通过定位错误警告和诊断信息返回源码的位置,从而确定是呐一行代码出现了错误,并且记录下来他的错误信息;
SourceLocation loc = decl->getLocation().getLocWithOffset(pos);
(8) 通过把保存下来的位置信息和需要显示的温馨提示内容传给Report()函数,实现在对应位置上进行提示错误信息(DiagnosticsEngine::Error – 诊断引擎下的错误Error)【D.getCustomDiagID – 获得对话框的ID信息】;
D.Report(loc, D.getCustomDiagID(DiagnosticsEngine::Error, "温馨提示:类名中不能带有下划线"));
5.创建第二个类MJASTConsumer – 生成树的用户模式
class MJASTConsumer: public ASTConsumer {
private:
MatchFinder matcher;
MJHandler handler;
public:
MJASTConsumer(CompilerInstance &ci) :handler(ci) {
matcher.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler);
}
void HandleTranslationUnit(ASTContext &context) {
matcher.matchAST(context);
}
};
(1)MJASTConsumer继承自ASTConsumer,通过MatchFinder和MJHandler创建两个成员;
(2)设计一个构造器;
6.创建第三个类MJASTAction – AST动作
class MJASTAction: public PluginASTAction {
public:
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
return unique_ptr<MJASTConsumer> (new MJASTConsumer(ci));
}
bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) {
return true;
}
};
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
return unique_ptr<MJASTConsumer> (new MJASTConsumer(ci));
}
创建了一个AST用户ASTConsumer,并且返回该用户
bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) {
return true;
}