前言
如果想要扩展clang
的特定操作,通过Attribute
(属性)是最便捷的方式,通过扩展属性,我们可以实现自定义的语义诊断,添加定制化语法检查,实现自己的OCLint
.具体实现方法往下看吧.
在clang中增加属性(attribute)
属性(Attribute
)是构成程序基本结构的元数据,开发者可以通过属性来给编译器传递必要的语义信息.例如,属性可以改变程序的代码生成结构,或者提供额外的静态分析的语义信息.下面我们来讨论如何在Clang
中增加自定义的属性.
属性的定义
Clang
中的属性被分为三个阶段来处理
- 解析为语法属性
- 语法属性到语义属性的转换
- 属性的语义分析
语法解析取决于属性所在的语法形式.一般属性是语法解析的会通过AttributeList
对象来表示.以列表形式来体现解析后的属性,该列表从属于声明符或声明的分隔符.属性的语法分析是Clang
自动进行的,但是关键字的属性除外,关键字的解析和AttributeList
对象的生成必须我们手动控制.
当Decl
和AttributeList
从语法属性转换为语义属性时,会调用Sema::ProcessDeclAttributeList()
方法.语法属性到语义属性的转换过程取决于属性的定义和属性的语义需要.最终,语义属性从属于Decl
对象,可以通过Decl::getAttr<T>()
来获得.
语义属性的结构也是由Attr.td
提供的属性的定义来决定的.该定义用于自动生成一系列方法,方法是用于实现对于的属性.例如clang::Attr
的衍生类,语法解析需要用的必要信息,某些属性的自动语义检查等.
属性的结构
通过查看属性文件Attr.td
(路径为include/clang/Basic/Attr.td
),通过一个集成自InheritableAttr
的属性示例
def Aligned : InheritableAttr {
let Spellings = [GCC<"aligned">, Declspec<"align">, Keyword<"alignas">,
Keyword<"_Alignas">];
// let Subjects = SubjectList<[NonBitField, NormalVar, Tag]>;
let Args = [AlignedArgument<"Alignment", 1>];
let Accessors = [Accessor<"isGNU", [GCC<"aligned">]>,
Accessor<"isC11", [Keyword<"_Alignas">]>,
Accessor<"isAlignas", [Keyword<"alignas">,
Keyword<"_Alignas">]>,
Accessor<"isDeclspec",[Declspec<"align">]>];
let Documentation = [Undocumented];
}
属性的基本参数有:Spellings
(拼写),Subjects
(主题),Documentation
(文档).
添加属性步骤
在Clang
中添加新属性的步骤分为以下几部分.
-
在
include/clang/Basic/Attr.td
中添加属性的定义.
该tablegen
的定义必须要属于Attr
类型.大多数属性都属于InheritableAttr
类型,该类型指明了该属性的可以被它关联的Decl
的重定义继承.
InheritableParamAttr
和InheritableAttr
的用法相似,主要不同在于前者属性通过参数来,后者通过声明.
当一个属性需要应用于类型而不是声明时,就应该属于TypeAttr
,此属性不会生成AST
表示(我们下面的内容不讨论类型属性).
继承自IgnoredAttr
的属性会被解析,并且生成一个忽略属性的诊断,该设计主要用于由第三方而非Clang
提供的属性. -
在
include/clang/Basic/AttrDocs.td
中添加文档定义. -
在
lib/Sema/SemaDeclAttr.cpp
中添加语义操作.
添加属性具体细节
以上介绍了添加属性需要的步骤,现在对步骤进行拆分,详细讲解每部分需要进行的具体操作.
属性的定义会指定几种关键信息,例如属性的语义名称,属性支持的拼写,属性的参数等等.大部分的Attr
派生类不会要求在声明时就定义所有的信息,但是每个属性至少要指明的内容有:拼写列表,主题列表和文档列表.
拼写列表
所有的属性都需要指明拼写列表,用于表示属性可以识别的拼写方式.例如,单个语义属性包含一个关键字拼写.空的拼写列表也是合法的,有时回用于属性的隐式创建.
主题列表
属性可以关联一个或多个Decl
主题.如果属性需要关联主题列表中没有的主题,会自动触发诊断信息.该诊断类型属于警告还是错误,取决于属性主题列表的定义,默认是警告.
向用户展示的诊断信息是通过列表中的主题自动判断的,主题列表也可以指明自定义的诊断参数.
主题列表生成的诊断类型为diag::warn_attribute_wrong_decl_type
或diag::err_attribute_wrong_decl_type
,如果之前在SubjectList
中加入过未使用的Decl
节点,对应的参数枚举可以在include/clang/Sema/AttributeList.h
中找到.自动判断诊断参数的逻辑可以在utils/TableGen/ClangAttrEmitter.cpp
中设置.
默认情况下,主题列表中的元素属于以下两个节点中的一种:DeclNodes.td
中的Decl
节点,StmtNodes.td
中的statement
节点.然而,复杂的主题可以通过SubsetSubject
对象来创建.每个SubsetSubject
对象都有一个基本的主题(一定是Decl
或Stmt
节点,而不是SubsetSubject
节点).例如NonBitField
的SubsetSubject
关联FieldDecl
,用于检测给的的FieldDecl
是否是bit field
.当SubsetSubject
在主题列表中声明时,必须提供自定义的诊断参数.
当HasCustomParsing
被设置为1
时,属性的主题列表的诊断校验会自动进行.
文档列表
所有的属性必须有关联的文档.文档是由服务端的公共网页服务器来生成的.一般属性文档的定义是在include/clang/Basic/AttrDocs.td
中生成的.
如果属性不允许公共预览或者是一个隐式生成的没有可视拼写的属性时,文档列表可以制定Undocumented
对象.否则就需要在AttrDocs.td
中添加属性的文档.
文档是由Document
生成的,所有的衍生类型必须指定文档的分类和文档本身,还可以指定属性的自定义头.
现在有四种预定义的文档分类:DocCatFunction
关联函数类主题,DocCatVariable
关联变量类主题, DocCatType
关联类型属性,DocCatStmt
表达式属性.自定义文档分类可以用于表达相同方法的一类属性.自定义分类的优势在于便于提供属性类簇的预览信息.例如用于注释的属性自定义分类DocCatConsumed
,用于解释注释的级别.
文档内容通过reStructuredText(RST)
语法来编写.
在属性文档书写完成后,需要在本地测试来确保不会在服务端生成文档时出现问题.本地测试需要通过clang-tblgen
来构建.为了生成属性文档,可以执行以下代码
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
中提交修改.该文件是由服务端自动生成的,在该文件中的任何改动之后都会被覆盖.
其他的属性
Attr
定义包含其他控制属性行为的元素.我们只讨论几种.
如果属性的语法解析较为复杂或者与语义解析出入较大时,可以通过设置HasCustomParsing
为1
,Parser::ParseGNUAttributeArgs()
的解析代码就可以在特定情况下进行更新.将该变量设置为1
需要进行额外的实现.
如果属性不想衍生出模板声明时,将Clone
设置为0
,模式所有的属性都会复制模板实例.
如果属性不想生成AST
节点,应该吧ASTNode
设置为0
.注意的是,所有继承TypeAttr
和IgnoredAttr
的节点都不会生成AST
节点.其他节点默认生成AST
节点.AST
节点是属性的语义表达形式.
LangOpts
指明属性的语言选项.
基于属性的拼写列表会自动生成属性的存取器.例如,一个属性有两种不同的拼写方式Foo
和Bar
.存取器会生成如下样式[Accessor<"isFoo", [GNU<"Foo">]>, Accessor<"isBar", [GNU<"Bar">]>]
.
如果属性不需要自定义语义操作,可以将SemaHandler
设置为0
.IgnoredAttr
自动不进行语义操作,其他属性默认都会进行语义操作.不进行语义操作的属性,不会给语法分析的属性提供Kind
枚举.
默认情况下,在合并声明的属性时,属性是不可重复的,如果一个属性在合并阶段允许重复,需要将DuplicatesAllowedWhileMerging
设置为1
.
样板文件
所有属性声明的语义分析都是在lib/Sema/SemaDeclAttr.cpp
进行的,一般是由ProcessDeclAttribute()
方法开始.如果属性是一个简单属性(不需要自定义的语义分析,只需要遵循默认提供的分析),调用handleSimpleAttribute<YourAttr>(S, D, Attr)
即可.否则,就应该编写新的handleYourAttr()
方法来增加转换的表达式.不要在属性的内部直接实现这些控制逻辑.
除非有特定的属性定义,常规的语法检查是自动进行的.常规检查包括诊断属性是否符合给定的Decl
,确定参数的输入数量.
如果属性增加了额外的警告,在include/clang/Basic/DiagnosticGroups.td
定义了DiagGroup
,在拼写后将_
替换为-
.如果只有一种检测时,可以通过使用DiagnosticSemaKinds.td
中的InGroup<DiagGroup<"your-attribute">>
来实现.
属性生成的所有的语法诊断,包括系统自动生成,都需要有对应的测试用例.
语义操作
大多数属性的实现都会对编译产生作用.例如,修改代码生成方式,或者增加额外的分析的语义检查.除了增加属性的定义和到语义表达式的转换,还需要实现自定义的属性的用法.
属性是否包含clang::Decl
对象可以通过hasAttr<T>()
方法来获得.想要获得属性的语义表达式可以通过getAttr<T>
.
网友评论