Clang插件
 
在编译时,Clang插件可运行额外的用户定义操作.
介绍
Clang插件在代码上运行FrontendActions.见FrontendAction教程,了解如何使用RecursiveASTVisitor编写FrontendAction.
 这里,演示如何编写简单的clang插件.
编写PluginASTAction插件
 
与编写普通FrontendActions的主要区别在,可处理插件命令行选项.PluginASTAction基类声明一个必须在插件中实现的ParseArgs方法.
bool ParseArgs(const CompilerInstance &CI,const std::vector<std::string>& args) {for (unsigned i = 0, e = args.size(); i != e; ++i) {if (args[i] == "-some-arg") {//处理命令行参数.}}return true;
}
注册插件
在运行时,编译器从动态库加载插件.要在库中注册插件,请使用FrontendPluginRegistry::Add<>:
static FrontendPluginRegistry::Add<MyPlugin> X("my-plugin-name", "my plugin description");
定义编译指示
插件还可通过声明PragmaHandler并使用PragmaHandlerRegistry::Add<>注册它,来定义编译指示:
//定义`#pragma example_pragma`的编译指示处理器
class ExamplePragmaHandler : public PragmaHandler {
public:ExamplePragmaHandler() : PragmaHandler("example_pragma") { }void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer, Token &PragmaTok) {//处理编译指示}
};
static PragmaHandlerRegistry::Add<ExamplePragmaHandler> Y("example_pragma","example pragma description");
定义属性
插件可通过声明ParsedAttrInfo并使用ParsedAttrInfoRegister::Add<>注册它来定义属性:
class ExampleAttrInfo : public ParsedAttrInfo {
public:ExampleAttrInfo() {Spellings.push_back({ParsedAttr::AS_GNU,"example"});}AttrHandling handleDeclAttribute(Sema &S, Decl *D, const ParsedAttr &Attr) const override {//处理属性return AttributeApplied;}
};
static ParsedAttrInfoRegistry::Add<ExampleAttrInfo> Z("example_attr","example attribute description");
插件属性必须定义的ParsedAttrInfo的成员包括:
1,拼写(Spellings),必须用属性的每个,都由属性语法及该语法的属性名拼写方式组成的拼写这里填充.
 如果语法允许域,则拼写必须为"scope::attr"(如果有域)或"::attr"(如果没有).
2,handleDeclAttribute,这是应用属性到声明的函数.它负责检查属性参数是否有效,且一般给Decl添加Attr来应用属性.
它返回AttributeApplied(指示已成功应用属性)或(失败)AttributeNotApplied.
根据属性,可能要定义的ParsedAttrInfo成员包括:
 1,NumArgs和OptArgs,设置属性的必需参数和可选参数个数.
 2,diagAppertainsToDecl,检查属性是否用在正确的声明类型上,如果没有,则发出诊断.
 3,diagLangOpts,检查当前语言模式是否允许该属性,如果禁止,则发出诊断.
 4,existsInTarget,检查给定目标是否允许该属性.
Attribute.cpp示例,可查看属性插件的工作示例.
放在一起
用打印顶级函数名来示例插件.此例已签入clang仓库;请查看最新版本的PrintFunctionNames.cpp.
运行插件
用编译器驱动
Clang驱动接受-fplugin选项来加载插件.Clang插件可通过fplugin-arg-<pluginname>-<argument>选项,从编译器驱动命令行接收参数.
这样,插件名自身不能包含破折号,但传递给插件的参数可以.
$ export BD=/path/to/build/directory
$ make -C $BD CallSuperAttr
$ clang++ -fplugin=$BD/lib/CallSuperAttr.so \-fplugin-arg-call_super_plugin-help \test.cpp
如果插件名包含破折号,请重命名插件或使用下面列举的cc1命令行选项.
使用cc1命令行
 
要运行插件,必须通过-load命令行选项,加载包含插件注册表的动态库.这加载所有已注册的插件,可通过指定-plugin选项来选择要运行的插件.
可用-plugin-arg-<plugin-name>传递插件的其他参数.
注意,这些选项必须到达clang的cc1进程.有两个方法:
 1,用-cc1选项直接调用解析过程;缺点是没有配置默认头文件搜索路径,因此要在命令行上指定完整的系统路径配置.
 2,如常使用clang,但在cc1进程的所有参数前面加上-Xclang.
如,要对clang中的源文件上运行print-function-names插件,请先构建该插件,然后用源码树中的插件调用clang:
$ export BD=/path/to/build/directory
$ (cd $BD && make PrintFunctionNames )
$ clang++ -D_GNU_SOURCE -D_DEBUG -D__STDC_CONSTANT_MACROS \-D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -D_GNU_SOURCE \-I$BD/tools/clang/include -Itools/clang/include -I$BD/include -Iinclude \tools/clang/tools/clang-check/ClangCheck.cpp -fsyntax-only \-Xclang -load -Xclang $BD/lib/PrintFunctionNames.so -Xclang \-plugin -Xclang print-fns
另见print-function-name插件示例的README这里
使用clang命令行
 
在clang命令行上,使用-fplugin=plugin,在cc1命令行上,按-load参数传递插件.如果插件类实现了getActionType方法,则会自动运行插件.
如,要在主AST操作后自动运行插件(即与用-add-plugin相同):
//在主`AST`操作后自动运行插件
PluginASTAction::ActionType getActionType() override {return AddAfterMainAction;
}
与-clear-ast-before-backend的交互
 
为了减少编译器的内存使用峰值,建议在一般是生成代码的主操作前,运行插件.这是因为在codegen操作后运行的插件,都会自动关闭-clear-ast-before-backend.
-clear-ast-before-backend通过在生成IR后和运行IR优化前,清理ClangAST来减少峰值内存.按getActionType,使用CmdlineBeforeMainAction或AddBeforeMainAction来运行插件,同样受益于-clear-ast-before-backend.
插件必须确保不修改AST,否则应在主操作之后运行它们.