Clang 之旅系列文章:php
Clang 之旅--使用 Xcode 开发 Clang 插件
Clang 之旅--[翻译]添加自定义的 attribute
Clang 之旅--实现一个自定义检查规范的 Clang 插件
html
这是 Clang 之旅系列的第二篇,本身想要完成的需求是:在编译阶段检查某个方法的参数与返回值的类型相同,若是类型不一致的话能抛出编译错误的提示。须要接触到 Clang 中关于 attribute 处理的代码,因此这篇先来翻译官方文档中添加自定义的 attribute 这一节,不得不说,虽然 Clang 的文档能够说是很标杆了,可是总有一种看了后面忘了前面的感受,多是 Clang 比较庞大,涉及专有词汇比较多的缘由,因此我会偏向意译多一点,试图用更加易懂的表达组织语言,也是加深本身的记忆吧。前端
attribute 是一种能够附加到程序结构中的数据形式,容许开发人员传递信息给编译器来实现各类需求。例如,attribute 能够用来改变在程序构造时生成的代码,或者用来提供额外的信息给静态分析。本文档讲解如何添加一个自定义的 attribute 到 Clang 中。现有 attribute 列表的文档能够在这里找到。服务器
Clang 中的 attribute 涉及到三个阶段:解析 attribute 、从已解析的 attribute 转换成语法树上的 attribute、对 attribute 进行处理。
attribute 的解析能够采用多种语法形式,例如 GNU、C++ 11 和 Microsoft 形式,还由 attribute 提供的其余信息来肯定。最终,解析好的 attribute 用一个 AttributeList
对象来表示。这些解析好的 attribute 会链成一个 attribute 链,加到声明或者定义上。attribtue 的解析是由 Clang 自动完成的,除了那些关键字 attribute。关键字的解析和 AttributeList
对象的生成必须由咱们手动完成。
最后,Sema::ProcessDeclAttributeList()
带着 Decl
类型和 AttributeList
类型的参数被调用,此时解析好的 attribute 就会被转化成语法树上的 attribute。这个处理依赖于 attribute 的属性定义和语义要求。最后的结果就是语法树上的 attribute 对象能够从 Decl
对象获取到,也就是经过调用 Decl::getAttr<T>()
来获取。
语法树上的 attribute 的结构一样也受到 Attr.td 文件中的定义所限制。这个定义会自动生成 attribute 的实现所用到的功能,包括生成 clang::Attr
的子类、解析器所用到的信息和某些 attribute 自动进行的语义分析等等。函数
添加新的 attribute 到 Clang 的第一个步骤就是把其定义添加到 include/clang/Basic/Attr.td。这个定义必须从 Attr
或者其子类继承。大多数 attribute 会直接从 InheritableAttr
继承,InheritableAttr
指定了这个 attribute 能够经过它所关联的 Decl
稍后进行重声明。若是这个 attribute 是做用于类型而不是声明,那么这种 attribute 应该从 TypeAttr
派生,而且一般不会被赋予 AST 表示(注意本文档并不讲解生成类型所用的 attribute)。一个继承于 IgnoredAttr
的 attribute 会被解析,可是会在被使用的时候产生一个 "被忽略的属性" 的警告,这种处理方法在某个属性支持别的前端而不支持 Clang 的状况下是颇有用的。
这个定义能指定 attribute 的一些关键部分,好比 attribute 的名字、attribute 支持的拼写、attribute 的参数等等。Attr
类型中的大多数成员变量都不须要派生定义,缺省的就足够了。可是,每一个 attribute 都须要至少指定 拼写列表、subject 列表和文档列表。测试
拼写 | 描述 |
---|---|
GNU | 用 GNU 风格 __attribute__((attr)) 语法和位置拼写 |
CXX11 | 用 C++ 风格 [[attr]] 语法拼写。若是该 attribute 是由 Clang 所使用的,那么应该设置命名空间为 "clang" |
Declspec | 用 Microsoft 风格 __declspec(attr) 语法拼写 |
Keyword | 这个 attribute 用关键字的方式拼写,而且须要自定义解析 |
GCC | 指定两种拼写:首先是 GNU 风格拼写;而后是 C++ 风格拼写,命名空间为 gnu 。只能为支持 GCC 的 attribute 指定这个拼写。 |
Pragma | attribute 用 #pragma 的形式拼写,而且须要在预处理器中执行自定义的处理。若是该 attribute 是由 Clang 所使用的,那么应该设置命名空间为 "clang" 。须要注意这个拼写并不能被用于声明语句中。 |
全部 attribute 都须要指定一个拼写列表,表示拼写 attribute 的方式。好比某个 attribute 可能会包含关键字拼写, C++11 拼写和 GNU 拼写。空的拼写列表也是容许的而且可能对隐式建立的 attribute 有用。如下是支持的拼写的表格:lua
拼写 | 描述 |
---|---|
GNU | 用 GNU 风格 __attribute__((attr)) 语法和位置拼写 |
CXX11 | 用 C++ 风格 [[attr]] 语法拼写。若是该 attribute 是由 Clang 所使用的,那么应该设置命名空间为 "clang" |
Declspec | 用 Microsoft 风格 __declspec(attr) 语法拼写 |
Keyword | 这个 attribute 用关键字的方式拼写,而且须要自定义解析 |
GCC | 指定两种拼写:首先是 GNU 风格拼写;而后是 C++ 风格拼写,命名空间为 gnu 。只能为支持 GCC 的 attribute 指定这个拼写。 |
Pragma | attribute 用 #pragma 的形式拼写,而且须要在预处理器中执行自定义的处理。若是该 attribute 是由 Clang 所使用的,那么应该设置命名空间为 "clang" 。须要注意这个拼写并不能被用于声明语句中。 |
每一个 attribute 都有一个或者多个 subject。若是 attribute 被使用到了一个不在 subject 列表上的 subject,就会自动显示诊断信息。 这个信息是警告仍是错误是由 attribute 中的 SubjectList
决定的,默认的是警告。显示给用户的诊断信息将根据 subject 列表自动肯定,可是也能够在 SubjectList
中指定自定义诊断参数。不符合 subject 列表致使的诊断信息要么是 diag::warn_attribute_wrong_decl_type
,要么是 diag::err_attribute_wrong_decl_type
。具体参数的枚举值能够从 include/clang/Sema/AttributeList.h 找到。若是先前未使用的 Decl
节点被添加到 SubjectList
中,则可能须要更新用于自动肯定 utils/TableGen/ClangAttrEmitter.cpp中的诊断参数的逻辑。
全部在 SubjectList 中的 subject 要么是在 DeclNodes.td
中定义的 Decl 节点,要么就是在 StmtNodes.td
中定义的 statement 节点。不过,能够生成 SubsetSubject
对象来建立更加复杂的 subject。每一个这样的对象都有一个它所属的基本对象(必须是一个 Decl 或 Stmt 节点,而不是一个 SubsetSubject 节点),还有一些自定义代码在肯定某个 attribute 是否属于该对象时被调用。例如,一个 NonBitField
SubsetSubject 关联到 FieldDecl
类,同时会测试给定的 FieldDecl 是不是一个位字段。当在 SubjectList 中指定了一个 SubsetSubject 时必须同时提供一个自定义的诊断信息参数。
attribute 的 subject 列表会在 HasCustomParsing
设为 1
的状况下自动进行诊断检查。spa
全部的 attribute 都必须具备某种形式的文档。文档是经过天天运行的服务器端进程在公共服务器上生成的。一般来讲,attribute 的文档是在 include/clang/Basic/AttrDocs.td中单独定义的,以文档属性命名。
若是 attribute 不是通用的,或者是隐式建立的没有对应拼写的 attribuet,则能够将文档列表变量设置为 Undocumented
。不然,该 attribute 应将其文档添加到 AttrDocs.td。
文档属性是从 Documentation
tablegen 类型继承而来的,全部的派生类型都必须建立一个文档类别和设置文档自己内容。此外,它还能够为 attribute 指定一个自定义的标题,不然会选择默认的标题。
如今有四种预先定义好的文档类别:DocCatFunction
对应函数的 attribute,DocCatVariable
对应到变量的 attribute,DocCatType
对应类型的 attribute,DocCatStmt
对应声明的 attribute。自定义文档类别应该用于具备相似功能的 attribute 组。自定义类别很是适合用来为组中的 attribute 提供概述信息。
文档内容(包括 attribute 的内容或者类别的内容)是用 reStructuredText(RST)格式写的。
在编写该 attribute 的文档以后,应该对其在本地对其进行测试,以确保在服务器上生成文档不会有问题。本地测试须要从新构建 clang-tblgen。要生成 attribute 文档,请执行如下命令:插件
clang-tblgen -gen-attr-docs -I /path/to/clang/include /path/to/clang/include/clang/Basic/Attr.td -o /path/to/clang/docs/AttributeReference.rst
复制代码
在本地进行测试时,不要对 AttributeReference.rst
提交更改。该文件是由服务器自动生成的,而且对该文件所作的任何更改都将被覆盖。翻译
attribute 能够选择指定能够传递给 attribute 的参数列表。attribute 的参数指定 attribute 的解析形式和语义形式。例如,若是 Args
是 [StringArgument<"Arg1">, IntArgument<"Arg2">]
,那么 __attribute__((myattribute("Hello", 3)))
就是一个合法的使用方式;这个 attribute 在解析时要求有两个参数:一个 string 类型一个 integer 类型。
每一个参数都有个名字和一个用来指定这个参数是否为可选的标志。参数关联的 C++ 类型由参数定义类型肯定。若是现有参数类型不足,则能够建立新类型,但须要修改 utils/TableGen/ClangAttrEmitter.cpp 才能正确支持该新类型。
Attr
的定义还具备其余变量来控制 attribute 的行为。其中有不少是用于特殊用途的,超出了本文档的范围,但有一些仍是值得提上一嘴的。
若是 attribute 的解析形式更加复杂或者和语义形式不一样,则能够将 HasCustomParsing
变量设置为 1
,而且能够针对特殊状况修改 Parser::ParseGNUAttributeArgs() 中的解析代码。请注意,这仅适用于具备 GNU 拼写的 attribute;__declspec 拼写的 attribute 如今是忽略这个标志的,并由 Parser::ParseMicrosoftDeclSpec
负责解析。
请注意,把 HasCustomParsing
设置为 1
将再也不使用通用的 attribute 处理逻辑,须要额外的处理来确保该 attribute 能使用。
若是该 attribute 不经过模板声明实例化,则将 Clone
成员变量设置为 0。默认状况下,全部的 attribute 都将经过模板进行实例化。
不须要 AST 节点的 attribute 应该将 ASTNode
变量设置为 0 以免污染 AST。请注意,从 TypeAttr
或 IgnoredAttr
继承的类都不会自动生成 AST 节点。全部其余属性默认会生成一个 AST 节点。该 AST 节点是 attribute 的语义表示。LangOpts
变量指定了 attribute 所需的语言选项列表。例如,全部的 CUDA-specific 的 attribuet 都将 LangOpts
字段指定为 [CUDA]
,而且当 CUDA 语言选项未启用时,会发出“attribute ignored”的警告诊断。因为语言选项不是自动生成的节点,所以必须手动建立新的语言选项,并应指定 LangOptions
类所使用的拼写。
能够基于 attribute 的拼写列表为该 attribute 生成自定义的存取器。例如,若是某个 attribute 有两种不一样的拼写:'foo' 和 'bar',则能够建立访问器:[Accessor<"isFoo", [GNU<"Foo">]>, Accessor<"isBar",[GNU<"Bar">]>]
。这些存取器将在该 attribute 的语义形式上生成,不接受任何参数并返回一个布尔值。
不须要自定义语义分析的 attribute 应该将 SemaHandler
变量设为 0
。请注意,任何从 IgnoredAttr
继承的 attribute 都不会自动进行语义处理。全部其余 attribute 都使用默认的语义处理。没有语义处理的 attribute 都不会有解析好的 attribute Kind
枚举器。
指定 Target 的 attribute 可能会与不一样 Target 的 attribute 共用一个拼写。例如,ARM 和 msp430 Target 都有一个拼写为 GNU<"interrupt">
的 attribute,但各自有不一样的解析方式和语义要求。为了支持这个特性,继承自 TargetSpecificAttribute
的 attribute 能够指定 ParseKind
变量。这个变量在共用拼写的全部参数之间应该是相同的,而且对应于解析 attribute 的 Kind
的枚举器。这容许 attribute 共用一种解析类型,但具备不一样的语义属性。例如,AttributeList::AT_Interrupt
是共用的解析类型,但 ARMInterruptAttr 和 MSP430InterruptAttr 是各自的语义属性。
默认状况下,当声明为 merging attribute 时,该 attributes 不会被复制。可是,若是在此合并阶段中能够复制某个 attribute,那么将 DuplicatesAllowedWhileMerging
变量设置为 1
,该 attribute 就会被合并。
默认状况下,attribute 的参数在上下文中被解析。若是应该在上下文中解析 attribute 的参数(相似于解析 sizeof
表达式的参数的方式),请将 ParseArgumentsAsUnevaluated
设置为 1
。
声明 attribute 的全部的语义处理都在文件 lib/Sema/SemaDeclAttr.cpp 中,而且一般都从 ProcessDeclAttribute()
函数开始。若是这个 attribute 是一个“简单的” attribute,也就是说这个 attribute 除了自动生成的内容以外不须要自定义的语义处理,那么就添加 handleSimpleAttribute<YourAttr>(S, D, Attr);
函数到 switch 语句中。不然,编写一个新的 handleYourAttr()
函数,并将其添加到 switch 语句中。不要直接在 case
语句中实现处理逻辑。
除非 attribute 的定义中另有规定,不然将自动处理解析 attribute 的常见语义检查,包括诊断不属于给定 Decl
的解析的 attribute、确保传递正确的最小数量的参数等等。
若是 attribute 要加上额外的警告,那么在 include/clang/Basic/DiagnosticGroups.td 文件中定义一个 DiagGroup
。若是只有一个诊断信息的话,直接在 DiagnosticSemaKinds.td 文件中使用 InGroup<DiagGroup<"your-attribute">>
也是能够的。
全部为你自定义的 attribute 所生成的诊断信息,包括自动生成的(好比 subject 和参数个数),都应该有一个对应的测试用例。
大多数 attribute 被实现为对编译器有必定的影响。例如,修改生成代码的方式,或为分析过程添加额外的语义检查等,将 attribute 的定义和转换添加到该 attribute 的语义表示中,剩下的就是实现 attribute 的自定义逻辑。
可使用 hasAttr<T>()
方法来查询 clang::Decl
对象中是否有 attribute。可使用 getAttr<T>
来获取一个指向 attribute 的指针。