Clang 之旅--[翻譯]新增自定義的 attribute
Clang 之旅系列文章:
Clang 之旅--使用 Xcode 開發 Clang 外掛
Clang 之旅--[翻譯]新增自定義的 attribute
Clang 之旅--實現一個自定義檢查規範的 Clang 外掛
前言
這是 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 列表和文件列表。
拼寫
所有 attribute 都需要指定一個拼寫列表,表示拼寫 attribute 的方式。比如某個 attribute 可能會包含關鍵字拼寫, C++11 拼寫和 GNU 拼寫。空的拼寫列表也是允許的並且可能對隱式建立的 attribute 有用。以下是支援的拼寫的表格:
拼寫 | 描述 |
---|---|
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
的情況下自動進行診斷檢查。
文件
所有的 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 的指標。
PS:我建了一個 Clang & LLVM 微信交流群,可以交流 Clang 相關問題、iOS 相關問題、發招聘資訊,拒絕廣告、拒絕刷屏。想要加入的可以加我微信拉你進群~
相關文章
- clang attribute
- [非專業翻譯] Mapster - 自定義對映
- [非專業翻譯] Mapster - 自定義命名約定
- [非專業翻譯] Mapster - 自定義對映邏輯
- go-carbon 1.3.2 版本釋出,新增自定義 i18n 方法和日文翻譯檔案Go
- [Flutter翻譯]如何在Backendless Flutter SDK中使用自定義類Flutter
- Aop+自定義註解實現資料字典翻譯
- [Flutter翻譯]使用BuiltValueSerializer建立自定義built_value序列化器FlutterUI
- clang++編譯命令編譯
- iOS 新增自定義的字型 Fonts provided by applicationiOSIDEAPP
- Laravel 新增自定義助手函式Laravel函式
- AUTOCAD——新增自定義填充圖案
- Azure AD(六)新增自定義域名
- Android 端如何新增自定義表情Android
- 給Product新增自定義屬性
- hanlp新增自定義字典的步驟介紹HanLP
- [譯] Part 31: Golang 中的自定義ErrorGolangError
- 【譯】CSS 自定義屬性的策略指南CSS
- # 翻譯:Shadow DOM隔離釋義
- NDK clang編譯器的一個bug編譯
- robotframework appiumLibrary新增自定義關鍵字FrameworkAPP
- Google Cloud IAM中新增自定義域名GoCloud
- ubuntu下OpenLDAP新增自定義屬性UbuntuLDA
- 公眾號新增自定義連結
- [譯] 從 0 建立自定義元素
- 如何在Kubernetes 裡新增自定義的 API 物件(一)API物件
- 線上文字翻譯能力新增14個直譯模型,打造以中文為軸心語言的翻譯系統模型
- 使用ADD_CUSTOM_COMMAND 新增自定義命令
- jQuery Validate新增自定義驗證規則jQuery
- 如何在React Native中新增自定義字型React Native自定義字型
- 『手撕Vue-CLI』新增自定義指令Vue
- apisix-dashboard上新增自定義外掛API
- SOLIDWORKS如何新增自定義標準件庫Solid
- ElementUI的Table表格新增自定義頭CheckBox多選框UI
- Android自定義View--翻書控制元件(一)AndroidView控制元件
- IdentityServer4-客戶端定義-翻譯IDEServer客戶端
- Apache 新增自定義vhost 目錄,等其他配置Apache
- wpf自定義控制元件新增引用資源控制元件