Clang 之旅—[翻譯]添加自定義的 attribute

Clang 之旅系列文章:php

Clang 之旅--使用 Xcode 開發 Clang 插件
Clang 之旅--[翻譯]添加自定義的 attribute
Clang 之旅--實現一個自定義檢查規範的 Clang 插件
html

前言

這是 Clang 之旅系列的第二篇,本身想要完成的需求是:在編譯階段檢查某個方法的參數與返回值的類型相同,若是類型不一致的話能拋出編譯錯誤的提示。須要接觸到 Clang 中關於 attribute 處理的代碼,因此這篇先來翻譯官方文檔中添加自定義的 attribute 這一節,不得不說,雖然 Clang 的文檔能夠說是很標杆了,可是總有一種看了後面忘了前面的感受,多是 Clang 比較龐大,涉及專有詞彙比較多的緣由,因此我會偏向意譯多一點,試圖用更加易懂的表達組織語言,也是加深本身的記憶吧。前端


怎樣添加 attribute

attribute 是一種能夠附加到程序結構中的數據形式,容許開發人員傳遞信息給編譯器來實現各類需求。例如,attribute 能夠用來改變在程序構造時生成的代碼,或者用來提供額外的信息給靜態分析。本文檔講解如何添加一個自定義的 attribute 到 Clang 中。現有 attribute 列表的文檔能夠在這裏找到。服務器

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 自動進行的語義分析等等。函數

include/clang/Basic/Attr.td

添加新的 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"。須要注意這個拼寫並不能被用於聲明語句中。
Subjects

每一個 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。請注意,從 TypeAttrIgnoredAttr 繼承的類都不會自動生成 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 的指針。

相關文章
相關標籤/搜索