前言
如果想要擴(kuò)展clang
的特定操作,通過(guò)Attribute
(屬性)是最便捷的方式,通過(guò)擴(kuò)展屬性,我們可以實(shí)現(xiàn)自定義的語(yǔ)義診斷,添加定制化語(yǔ)法檢查,實(shí)現(xiàn)自己的OCLint
.具體實(shí)現(xiàn)方法往下看吧.
在clang中增加屬性(attribute)
屬性(Attribute
)是構(gòu)成程序基本結(jié)構(gòu)的元數(shù)據(jù),開(kāi)發(fā)者可以通過(guò)屬性來(lái)給編譯器傳遞必要的語(yǔ)義信息.例如,屬性可以改變程序的代碼生成結(jié)構(gòu),或者提供額外的靜態(tài)分析的語(yǔ)義信息.下面我們來(lái)討論如何在Clang
中增加自定義的屬性.
屬性的定義
Clang
中的屬性被分為三個(gè)階段來(lái)處理
- 解析為語(yǔ)法屬性
- 語(yǔ)法屬性到語(yǔ)義屬性的轉(zhuǎn)換
- 屬性的語(yǔ)義分析
語(yǔ)法解析取決于屬性所在的語(yǔ)法形式.一般屬性是語(yǔ)法解析的會(huì)通過(guò)AttributeList
對(duì)象來(lái)表示.以列表形式來(lái)體現(xiàn)解析后的屬性,該列表從屬于聲明符或聲明的分隔符.屬性的語(yǔ)法分析是Clang
自動(dòng)進(jìn)行的,但是關(guān)鍵字的屬性除外,關(guān)鍵字的解析和AttributeList
對(duì)象的生成必須我們手動(dòng)控制.
當(dāng)Decl
和AttributeList
從語(yǔ)法屬性轉(zhuǎn)換為語(yǔ)義屬性時(shí),會(huì)調(diào)用Sema::ProcessDeclAttributeList()
方法.語(yǔ)法屬性到語(yǔ)義屬性的轉(zhuǎn)換過(guò)程取決于屬性的定義和屬性的語(yǔ)義需要.最終,語(yǔ)義屬性從屬于Decl
對(duì)象,可以通過(guò)Decl::getAttr<T>()
來(lái)獲得.
語(yǔ)義屬性的結(jié)構(gòu)也是由Attr.td
提供的屬性的定義來(lái)決定的.該定義用于自動(dòng)生成一系列方法,方法是用于實(shí)現(xiàn)對(duì)于的屬性.例如clang::Attr
的衍生類(lèi),語(yǔ)法解析需要用的必要信息,某些屬性的自動(dòng)語(yǔ)義檢查等.
屬性的結(jié)構(gòu)
通過(guò)查看屬性文件Attr.td
(路徑為include/clang/Basic/Attr.td
),通過(guò)一個(gè)集成自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];
}
屬性的基本參數(shù)有:Spellings
(拼寫(xiě)),Subjects
(主題),Documentation
(文檔).
添加屬性步驟
在Clang
中添加新屬性的步驟分為以下幾部分.
在
include/clang/Basic/Attr.td
中添加屬性的定義.
該tablegen
的定義必須要屬于Attr
類(lèi)型.大多數(shù)屬性都屬于InheritableAttr
類(lèi)型,該類(lèi)型指明了該屬性的可以被它關(guān)聯(lián)的Decl
的重定義繼承.
InheritableParamAttr
和InheritableAttr
的用法相似,主要不同在于前者屬性通過(guò)參數(shù)來(lái),后者通過(guò)聲明.
當(dāng)一個(gè)屬性需要應(yīng)用于類(lèi)型而不是聲明時(shí),就應(yīng)該屬于TypeAttr
,此屬性不會(huì)生成AST
表示(我們下面的內(nèi)容不討論類(lèi)型屬性).
繼承自IgnoredAttr
的屬性會(huì)被解析,并且生成一個(gè)忽略屬性的診斷,該設(shè)計(jì)主要用于由第三方而非Clang
提供的屬性.在
include/clang/Basic/AttrDocs.td
中添加文檔定義.在
lib/Sema/SemaDeclAttr.cpp
中添加語(yǔ)義操作.
添加屬性具體細(xì)節(jié)
以上介紹了添加屬性需要的步驟,現(xiàn)在對(duì)步驟進(jìn)行拆分,詳細(xì)講解每部分需要進(jìn)行的具體操作.
屬性的定義會(huì)指定幾種關(guān)鍵信息,例如屬性的語(yǔ)義名稱(chēng),屬性支持的拼寫(xiě),屬性的參數(shù)等等.大部分的Attr
派生類(lèi)不會(huì)要求在聲明時(shí)就定義所有的信息,但是每個(gè)屬性至少要指明的內(nèi)容有:拼寫(xiě)列表,主題列表和文檔列表.
拼寫(xiě)列表
所有的屬性都需要指明拼寫(xiě)列表,用于表示屬性可以識(shí)別的拼寫(xiě)方式.例如,單個(gè)語(yǔ)義屬性包含一個(gè)關(guān)鍵字拼寫(xiě).空的拼寫(xiě)列表也是合法的,有時(shí)回用于屬性的隱式創(chuàng)建.
主題列表
屬性可以關(guān)聯(lián)一個(gè)或多個(gè)Decl
主題.如果屬性需要關(guān)聯(lián)主題列表中沒(méi)有的主題,會(huì)自動(dòng)觸發(fā)診斷信息.該診斷類(lèi)型屬于警告還是錯(cuò)誤,取決于屬性主題列表的定義,默認(rèn)是警告.
向用戶(hù)展示的診斷信息是通過(guò)列表中的主題自動(dòng)判斷的,主題列表也可以指明自定義的診斷參數(shù).
主題列表生成的診斷類(lèi)型為diag::warn_attribute_wrong_decl_type
或diag::err_attribute_wrong_decl_type
,如果之前在SubjectList
中加入過(guò)未使用的Decl
節(jié)點(diǎn),對(duì)應(yīng)的參數(shù)枚舉可以在include/clang/Sema/AttributeList.h
中找到.自動(dòng)判斷診斷參數(shù)的邏輯可以在utils/TableGen/ClangAttrEmitter.cpp
中設(shè)置.
默認(rèn)情況下,主題列表中的元素屬于以下兩個(gè)節(jié)點(diǎn)中的一種:DeclNodes.td
中的Decl
節(jié)點(diǎn),StmtNodes.td
中的statement
節(jié)點(diǎn).然而,復(fù)雜的主題可以通過(guò)SubsetSubject
對(duì)象來(lái)創(chuàng)建.每個(gè)SubsetSubject
對(duì)象都有一個(gè)基本的主題(一定是Decl
或Stmt
節(jié)點(diǎn),而不是SubsetSubject
節(jié)點(diǎn)).例如NonBitField
的SubsetSubject
關(guān)聯(lián)FieldDecl
,用于檢測(cè)給的的FieldDecl
是否是bit field
.當(dāng)SubsetSubject
在主題列表中聲明時(shí),必須提供自定義的診斷參數(shù).
當(dāng)HasCustomParsing
被設(shè)置為1
時(shí),屬性的主題列表的診斷校驗(yàn)會(huì)自動(dòng)進(jìn)行.
文檔列表
所有的屬性必須有關(guān)聯(lián)的文檔.文檔是由服務(wù)端的公共網(wǎng)頁(yè)服務(wù)器來(lái)生成的.一般屬性文檔的定義是在include/clang/Basic/AttrDocs.td
中生成的.
如果屬性不允許公共預(yù)覽或者是一個(gè)隱式生成的沒(méi)有可視拼寫(xiě)的屬性時(shí),文檔列表可以制定Undocumented
對(duì)象.否則就需要在AttrDocs.td
中添加屬性的文檔.
文檔是由Document
生成的,所有的衍生類(lèi)型必須指定文檔的分類(lèi)和文檔本身,還可以指定屬性的自定義頭.
現(xiàn)在有四種預(yù)定義的文檔分類(lèi):DocCatFunction
關(guān)聯(lián)函數(shù)類(lèi)主題,DocCatVariable
關(guān)聯(lián)變量類(lèi)主題, DocCatType
關(guān)聯(lián)類(lèi)型屬性,DocCatStmt
表達(dá)式屬性.自定義文檔分類(lèi)可以用于表達(dá)相同方法的一類(lèi)屬性.自定義分類(lèi)的優(yōu)勢(shì)在于便于提供屬性類(lèi)簇的預(yù)覽信息.例如用于注釋的屬性自定義分類(lèi)DocCatConsumed
,用于解釋注釋的級(jí)別.
文檔內(nèi)容通過(guò)reStructuredText(RST)
語(yǔ)法來(lái)編寫(xiě).
在屬性文檔書(shū)寫(xiě)完成后,需要在本地測(cè)試來(lái)確保不會(huì)在服務(wù)端生成文檔時(shí)出現(xiàn)問(wèn)題.本地測(cè)試需要通過(guò)clang-tblgen
來(lái)構(gòu)建.為了生成屬性文檔,可以執(zhí)行以下代碼
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
本地測(cè)試完成后,不用在AttributeReference.rst
中提交修改.該文件是由服務(wù)端自動(dòng)生成的,在該文件中的任何改動(dòng)之后都會(huì)被覆蓋.
其他的屬性
Attr
定義包含其他控制屬性行為的元素.我們只討論幾種.
如果屬性的語(yǔ)法解析較為復(fù)雜或者與語(yǔ)義解析出入較大時(shí),可以通過(guò)設(shè)置HasCustomParsing
為1
,Parser::ParseGNUAttributeArgs()
的解析代碼就可以在特定情況下進(jìn)行更新.將該變量設(shè)置為1
需要進(jìn)行額外的實(shí)現(xiàn).
如果屬性不想衍生出模板聲明時(shí),將Clone
設(shè)置為0
,模式所有的屬性都會(huì)復(fù)制模板實(shí)例.
如果屬性不想生成AST
節(jié)點(diǎn),應(yīng)該吧ASTNode
設(shè)置為0
.注意的是,所有繼承TypeAttr
和IgnoredAttr
的節(jié)點(diǎn)都不會(huì)生成AST
節(jié)點(diǎn).其他節(jié)點(diǎn)默認(rèn)生成AST
節(jié)點(diǎn).AST
節(jié)點(diǎn)是屬性的語(yǔ)義表達(dá)形式.
LangOpts
指明屬性的語(yǔ)言選項(xiàng).
基于屬性的拼寫(xiě)列表會(huì)自動(dòng)生成屬性的存取器.例如,一個(gè)屬性有兩種不同的拼寫(xiě)方式Foo
和Bar
.存取器會(huì)生成如下樣式[Accessor<"isFoo", [GNU<"Foo">]>, Accessor<"isBar", [GNU<"Bar">]>]
.
如果屬性不需要自定義語(yǔ)義操作,可以將SemaHandler
設(shè)置為0
.IgnoredAttr
自動(dòng)不進(jìn)行語(yǔ)義操作,其他屬性默認(rèn)都會(huì)進(jìn)行語(yǔ)義操作.不進(jìn)行語(yǔ)義操作的屬性,不會(huì)給語(yǔ)法分析的屬性提供Kind
枚舉.
默認(rèn)情況下,在合并聲明的屬性時(shí),屬性是不可重復(fù)的,如果一個(gè)屬性在合并階段允許重復(fù),需要將DuplicatesAllowedWhileMerging
設(shè)置為1
.
樣板文件
所有屬性聲明的語(yǔ)義分析都是在lib/Sema/SemaDeclAttr.cpp
進(jìn)行的,一般是由ProcessDeclAttribute()
方法開(kāi)始.如果屬性是一個(gè)簡(jiǎn)單屬性(不需要自定義的語(yǔ)義分析,只需要遵循默認(rèn)提供的分析),調(diào)用handleSimpleAttribute<YourAttr>(S, D, Attr)
即可.否則,就應(yīng)該編寫(xiě)新的handleYourAttr()
方法來(lái)增加轉(zhuǎn)換的表達(dá)式.不要在屬性的內(nèi)部直接實(shí)現(xiàn)這些控制邏輯.
除非有特定的屬性定義,常規(guī)的語(yǔ)法檢查是自動(dòng)進(jìn)行的.常規(guī)檢查包括診斷屬性是否符合給定的Decl
,確定參數(shù)的輸入數(shù)量.
如果屬性增加了額外的警告,在include/clang/Basic/DiagnosticGroups.td
定義了DiagGroup
,在拼寫(xiě)后將_
替換為-
.如果只有一種檢測(cè)時(shí),可以通過(guò)使用DiagnosticSemaKinds.td
中的InGroup<DiagGroup<"your-attribute">>
來(lái)實(shí)現(xiàn).
屬性生成的所有的語(yǔ)法診斷,包括系統(tǒng)自動(dòng)生成,都需要有對(duì)應(yīng)的測(cè)試用例.
語(yǔ)義操作
大多數(shù)屬性的實(shí)現(xiàn)都會(huì)對(duì)編譯產(chǎn)生作用.例如,修改代碼生成方式,或者增加額外的分析的語(yǔ)義檢查.除了增加屬性的定義和到語(yǔ)義表達(dá)式的轉(zhuǎn)換,還需要實(shí)現(xiàn)自定義的屬性的用法.
屬性是否包含clang::Decl
對(duì)象可以通過(guò)hasAttr<T>()
方法來(lái)獲得.想要獲得屬性的語(yǔ)義表達(dá)式可以通過(guò)getAttr<T>
.