实现自定义OCLint:静态代码检查-自定义clang的Attribute

前言

如果想要扩展clang的特定操作,通过Attribute(属性)是最便捷的方式,通过扩展属性,我们可以实现自定义的语义诊断,添加定制化语法检查,实现自己的OCLint.具体实现方法往下看吧.

在clang中增加属性(attribute)

属性(Attribute)是构成程序基本结构的元数据,开发者可以通过属性来给编译器传递必要的语义信息.例如,属性可以改变程序的代码生成结构,或者提供额外的静态分析的语义信息.下面我们来讨论如何在Clang中增加自定义的属性.

属性的定义

Clang中的属性被分为三个阶段来处理

  1. 解析为语法属性
  2. 语法属性到语义属性的转换
  3. 属性的语义分析

语法解析取决于属性所在的语法形式.一般属性是语法解析的会通过AttributeList对象来表示.以列表形式来体现解析后的属性,该列表从属于声明符或声明的分隔符.属性的语法分析是Clang自动进行的,但是关键字的属性除外,关键字的解析和AttributeList对象的生成必须我们手动控制.

DeclAttributeList从语法属性转换为语义属性时,会调用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中添加新属性的步骤分为以下几部分.

  1. include/clang/Basic/Attr.td中添加属性的定义.
    tablegen的定义必须要属于Attr类型.大多数属性都属于InheritableAttr类型,该类型指明了该属性的可以被它关联的Decl的重定义继承.
    InheritableParamAttrInheritableAttr的用法相似,主要不同在于前者属性通过参数来,后者通过声明.
    当一个属性需要应用于类型而不是声明时,就应该属于TypeAttr,此属性不会生成AST表示(我们下面的内容不讨论类型属性).
    继承自IgnoredAttr的属性会被解析,并且生成一个忽略属性的诊断,该设计主要用于由第三方而非Clang提供的属性.

  2. include/clang/Basic/AttrDocs.td中添加文档定义.

  3. lib/Sema/SemaDeclAttr.cpp中添加语义操作.

添加属性具体细节

以上介绍了添加属性需要的步骤,现在对步骤进行拆分,详细讲解每部分需要进行的具体操作.

属性的定义会指定几种关键信息,例如属性的语义名称,属性支持的拼写,属性的参数等等.大部分的Attr派生类不会要求在声明时就定义所有的信息,但是每个属性至少要指明的内容有:拼写列表,主题列表文档列表.

拼写列表

所有的属性都需要指明拼写列表,用于表示属性可以识别的拼写方式.例如,单个语义属性包含一个关键字拼写.空的拼写列表也是合法的,有时回用于属性的隐式创建.

主题列表

属性可以关联一个或多个Decl主题.如果属性需要关联主题列表中没有的主题,会自动触发诊断信息.该诊断类型属于警告还是错误,取决于属性主题列表的定义,默认是警告.
向用户展示的诊断信息是通过列表中的主题自动判断的,主题列表也可以指明自定义的诊断参数.
主题列表生成的诊断类型为diag::warn_attribute_wrong_decl_typediag::err_attribute_wrong_decl_type,如果之前在SubjectList中加入过未使用的Decl节点,对应的参数枚举可以在include/clang/Sema/AttributeList.h中找到.自动判断诊断参数的逻辑可以在utils/TableGen/ClangAttrEmitter.cpp中设置.

默认情况下,主题列表中的元素属于以下两个节点中的一种:DeclNodes.td中的Decl节点,StmtNodes.td中的statement节点.然而,复杂的主题可以通过SubsetSubject对象来创建.每个SubsetSubject对象都有一个基本的主题(一定是DeclStmt节点,而不是SubsetSubject节点).例如NonBitFieldSubsetSubject关联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定义包含其他控制属性行为的元素.我们只讨论几种.
如果属性的语法解析较为复杂或者与语义解析出入较大时,可以通过设置HasCustomParsing1,Parser::ParseGNUAttributeArgs()的解析代码就可以在特定情况下进行更新.将该变量设置为1需要进行额外的实现.

如果属性不想衍生出模板声明时,将Clone设置为0,模式所有的属性都会复制模板实例.

如果属性不想生成AST节点,应该吧ASTNode设置为0.注意的是,所有继承TypeAttrIgnoredAttr的节点都不会生成AST节点.其他节点默认生成AST节点.AST节点是属性的语义表达形式.

LangOpts指明属性的语言选项.

基于属性的拼写列表会自动生成属性的存取器.例如,一个属性有两种不同的拼写方式FooBar.存取器会生成如下样式[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>.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容

  • 前言 2000年,伊利诺伊大学厄巴纳-香槟分校(University of Illinois at Urbana-...
    星光社的戴铭阅读 15,846评论 8 180
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,836评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • 阿里是一个很成功的人,他是大企业的董事长,而且与一般的董事长不一样,他是几乎不必操心任何事情的董事长,他只要待在家...
    杨大辉阅读 765评论 0 6
  • 1 刚才专门数了一下,我一共有96个微信群。除了个别群之外,这些群主要涉及两个圈子,俄语圈子、文学圈子。 我是个俄...
    熊艺楸阅读 745评论 3 2