RFC-0057:預設沒有控制代碼

RFC-0057:預設無控點
狀態已接受
領域
  • FIDL
說明

我們建議預設在 FIDL 類型宣告中禁止使用帳號代碼,並新增關鍵字資源來標示可以包含帳號代碼或其他資源類型的類型。新增或移除資源修飾符可能是原始碼破壞性變更。

作者
提交日期 (年月分)2020-01-16
審查日期 (年-月-日)2020-01-23

「別再手光,太厲害了!」

摘要

我們建議預設禁止在 FIDL 類型宣告中使用控點,並新增關鍵字 resource1 來標記允許包含帳號代碼或其他資源類型的類型。新增或移除 resource 修飾符可能是原始碼破壞性變更。

提振精神

FIDL 的一項獨特功能是支援 Zircon 處理常式。帳號代碼在記憶體中是 32 位元整數,但系統會將其處理,也就是必須移動而不是複製起來,而且必須關閉,以免資源外洩。這種特殊處理方式會在考慮僅供不需處理且無需處理的純文字資料的功能時,產生問題。雖然 FIDL 繫結可以根據帳號代碼有條件地啟用程式碼,但這樣做會破壞可變性保證,因此並非理想的做法。舉例來說,在資料表中新增欄位通常都很安全,但新增控制代碼會導致原始碼破壞,不僅對該資料表,還適用於連帶包含該資料表的所有類型。這會將繫結推送繫結至保守,且一律假設類型可能包含控點,即使程式庫作者從未打算新增這些代碼也一樣。

需要因應帳號代碼的需求有以下好處:

  • 在 Dart 中,我們努力將 FIDL 導入 JSON 編碼收到的回傳,因為它僅適用於沒有帳號代碼的類型,並降低效能。此方法最終是使用 MaxHandles 屬性建構,但這是暫時性的解決方案,因為此屬性僅適用於最外層的類型,而非從該屬性間接存取的所有類型。
  • 在 Rust 中,第一次為類型新增控制代碼會破壞原始碼,因為該類型不會再衍生出 Clone 特徵。(如要正確複製帳號代碼,請叫用 zx_handle_duplicate syscall,而可能會失敗)。
  • 通訊協定的 Rust 繫結會透過可變動的參照和「零」控制代碼取用 FIDL 物件,而不是明確取得擁有權,因此沒有控點的物件之後可以重複使用。

如果我們要求程式庫作者指明類型是否可能包含帳號代碼,以及是否應變更指標,我們預期這些情況都會以更安全、更符合人體工學的方式處理這些情況,

設計

術語

FIDL 類型可以是類型或資源類型。資源類型包括:

  • handlehandle<H>,其中 H 是控制代碼子類型
  • Prequest<P>,其中 P 是通訊協定名稱
  • 使用 resource 修飾符宣告的結構體、資料表或聯集
  • 參照資源類型的類型別名
  • 包裝資源類型的新類型 RFC-0052
  • T?,其中 T 是不可為空值的資源類型
  • array<T>vector<T>,其中 T 是資源類型

所有其他類型都是值類型。

正確使用 resource 修飾符時,值類型一律不會包含處理常式,而資源類型可能 (現在或日後) 會包含控點。

語言

新的修飾符 resource 可套用至結構、資料表和聯集宣告。

如果沒有 resource,宣告就無法包含資源類型。FIDL 編譯器必須驗證這一點。只需要檢查直接欄位:如果 A 包含 B,也沒有標示為資源,且 B 包含控制代碼,則編譯作業會因 B 而失敗,且不需要另外針對包含控制代碼的 A 另外顯示相關錯誤訊息。

使用 resource 時,宣告可以包含資源類型。這個程式碼宣告的新類型即使不含資源,也會視為資源類型。

原則上,該語言允許對 newtype 宣告 RFC-0052 使用 resource。不過,包裝值類型的資源新類型並無實際用途,因此新類型會間接繼承其包裝類型的值/資源狀態。

文法

這個提案修改了 FIDL 文法中的一項規則:

declaration-modifiers = "strict" | "resource" ;

JSON IR

本提案將含有布林值的鍵 "resource" 新增至 "struct_declarations""table_declarations""union_declarations" 陣列中的所有物件。

請注意,這個金鑰與 "max_handles" 不會重複。值類型必須將 max_handles 設為零,但資源類型的 max_handles 可以任意數量,因為它反映了宣告的實際內容 (而非程式庫作者允許處理的意圖)。

繫結

本提案不包含關於繫結的具體變更。但它可讓 FIDL 繫結作者 (包括 FIDL 團隊) 解決在動機中討論的問題。以下提供幾個由這個 FTP 提供的範例,但不接受此類 FTP:

  • 針對值類型 (或更可能為 FIDL 文字格式,而非 JSON 格式) 實作 JSON 序列化和序列化,如 RFC-0058 中所述。
  • 針對值/資源類型的 C++ Clone() 方法使用不同的類型簽名,強調只有資源複製功能可能會失敗。
  • 讓 Rust 通訊協定將值型引數做為 &T,並將資源類型引數視為 T,而不是同時對資源類型使用 &mut T,也不要變更。

API 評分量表

API 評分量表應提供使用 resource 的時機指引。以下提供一些簡易案例:

  • 不含資源類型的結構體「不應」標示為 resource,因為 struct 無法擴充 (在大多數情況下新增控制代碼通常會導致 ABI 中斷)。
  • 不含資源類型的嚴格資料表或聯集「不應」標示為 resource,因為嚴格度已告知修改欄位屬於來源破壞性變更。

並解決原本沒有控點的彈性資料表和聯集的情況。舉例來說,我們可能希望根據程式庫的用途、使用程度、預期語言變更來源的預期成本,以及其他因素,建議在其中一側或另一側執行系統。

執行策略

高階導入步驟包括:

  • 剖析 fidlc 中的 resource 關鍵字。
  • 遷移現有的 FIDL 程式庫以使用 resource (詳情請參閱「不明」一節。
  • 利用測試在 fidlc 中驗證值/資源類型規則。
  • resource 旗標儲存在 JSON IR 中,並公開於 fidlgen。

人體工學

這項提案引進了新概念,使 FIDL 更加複雜。不同於「struct」和「通訊協定」等其他 FIDL 結構,新使用者不可能猜測「資源」的意義,因此需要從說明文件中學習。

我們可以對此提案會調整 FIDL 語言是否改變人體工學。這種做法有助於突顯包含控點的宣告,尤其是在巢狀結構中隱藏實際處理值的情況。任何查看程式庫的使用者都會立即看到結構具有處理能力,而不只是資料。另一方面,對於是否要使用 resource 和輸入關鍵字,可能不太人體工學。將一個宣告從值變更為資源可能會令人感到痛苦,而許多類型都必須成為資源 (儘管這看似很好,否則會顯示為來源中斷)。

藉由改善 FIDL 繫結,增加的複雜性便由其解釋。您可以自由為值類型和資源類型提供不同的 API,讓繫結更安全、更符合人體工學。如需這些改善項目的範例,請參閱「繫結」一節。

說明文件與範例

需要完成以下工作:

  • 更新所有與控制代碼相關的說明文件,視情況使用 resource
  • 更新 FIDL 語言規格以說明 resource 修飾符。
  • 在 FIDL 教學課程中提及 resource。您應該有一個簡要說明所有修飾符的簡短訊息 (也就是 strictresource)。
  • 說明如何將不含控點的新類型視為資源。
  • 繫結利用值/資源的差異後,請更新說明文件,以指出各值類型和資源類型提供的 API 之間的差異,並提供在兩者之間轉換的操作說明 (如果可以的話)。

回溯相容性

本提案不會影響 ABI 相容性。

修訂條款 (2021 年 7 月)。在實作過程中,我們在本提案與 RFC-0033:處理不明欄位和嚴格度的互動中發現了極端案例。在解碼資料表和彈性聯集時,部分繫結會儲存未知的成員;如果不明成員包含控點,值類型便無法這麼做,因此在這種情況下,解碼作業必須失敗。詳情請參閱相容性指南

修訂條款 (2021 年 10 月)。RFC-0137:在 FIDL 中捨棄不明資料後,繫結就不會再儲存不明資料,因此不會再出現邊緣情況。因此,值/資源的差異不會影響 ABI 相容性。

新增或移除 resource 修飾符之後,就與來源相容或無法轉換2,這與 RFC-0024 的關係。這些繫結明確允許為兩種類型產生不相容的 API,這種 API 只有在修飾符出現時才會不同,實際上,可能無法編寫可在新增/移除修飾符前後編譯的程式碼。如果程式庫作者希望以來源相容方式轉換至 resource,必須建立新的類型和方法,而不是變更現有類型和方法。

繫結作者開始利用其值/資源區別後,我們將重新審視這項決定。要求一個可轉換的路徑 (也許與 [Transitional] 屬性搭配使用中繼階段) 可能是值得的。一開始,我們並不清楚:這可能過於嚴格,也難以實現這個提案的潛在 API 改善項目。

效能

本提案對建構效能幾乎沒有影響:FIDL 編譯器將進行略為剖析新關鍵字並驗證其使用情況。此方法不會對 IPC 效能產生直接影響。如果繫結使用值/資源區別來建立 API,避免不必要的複製,則某些語言可能會稍微改善。舉例來說,您不需要複製值類型物件就能多次傳送該物件。

安全性

本提案並未直接影響安全性,而是可讓繫結功能提供更安全的 API。舉例來說,C++ 可以為含有 [[nodiscard]] 的資源類型在 Clone() 上強制處理錯誤,或藉由移動動作,Rust 可能會採用資源類型方法引數,防止意外使用變動物件。這類變更可能會導致錯誤 (包括安全性錯誤)。

測試

這項功能的測試方式如下:

  • 在 fidlc 中新增剖析和驗證程式碼路徑的測試。這些測試應在各種情況下執行,例如標示為 resource (或非) 宣告無法符合資源類型 (或值類型) 的定義。
  • 除了修正需要 resource 的現有宣告之外,在黃金製造中新增一些資源類型宣告。
  • 更新 fidl-changes 測試套件,示範從值類型轉換至資源類型的步驟,反之亦然。

缺點、替代與不明

這個提案加入了新的關鍵字,這會讓語言變得更加複雜。使用過多關鍵字可能會造成問題;「嚴格的資源聯集」還算有點麻煩。

本提案以下列兩種方式降低 FIDL 的可變性保證:

  • 以往,為類型新增控制代碼不會是來源破壞性變更。現在,這是明確允許且預期的情況 (除非類型將類型標示為 resource,以免需要新增控制代碼)。
  • 以往,您可以先為類型宣告類型,例如 (1) 為其加入控點,以及 (2) 能夠將該類型納入任何其他類型的欄位。現在,圖書館作者必須在開始時選擇 (1) 到 (2) 之間。

這項提案有兩個主要替代方案:

  • 不採取任何行動。允許在任何位置使用控點,並確保在新增或移除控點時,繫結必須維持原始碼相容性。
  • 預設允許控點。如同本提案,假設宣告是資源類型,且需要 value 關鍵字來禁止在宣告中使用資源類型。

「Motivation」和「Ergonomics」各部分認為不執行任何動作。至於其他替代方案,經驗顯示大多數訊息都不含帳號代碼,而且在通訊協定中傳遞帳號代碼需要預先規劃及預先規劃。換句話說,值類型是常見情況,之後添加控點的功能可能就沒那麼實用。表明不允許帳號代碼是更好的預設設定。

本提案主要將造福使用 FIDL 繫結的開發人員;而缺點則適用於設計 API 的程式庫作者。這項取捨與 Fuchsia API Council Charter 接手,讓開發人員優先於 API 設計人員和實作者。

但建議採用另一個方法:處理參考檔案。比起將值類型停權,不如將控點當做參照來解決值/資源問題。複製包含控制代碼的結構時,系統會對同一個帳號代碼建立另一個參照。您可以在 C++ 中使用 shared_ptr 完成這項作業,不必新增 resource 關鍵字,也能大幅簡化工作。但其挑戰如下:

  • 所有繫結都需要簿記機制,確保只有在最後一個參照消失時,控制代碼才會關閉。這對某些語言來說並不容易。
  • 將控制代碼傳送至其他程序後,對該程序的所有其他參照都會失效,例如懸浮指標。在這類情況下,處理以一般值的處理方式更為方便,這代表在編譯時間方面,安全性較低。
  • 由於這樣做需要變更所有帳號代碼的類型,因此對於所有語言而言,這都有可能是破壞性變更。如要順利轉換,就必須執行許多工作。

本提案仍有幾個待解決的問題:

  • 該如何遷移現有的 FIDL 程式庫?使用 resource 標示「所有」現有宣告是安全的,但不會反映程式庫作者的意圖。雖然只標示最低限度 (例如包含帳號代碼的類型) 雖然可行,但也可能過於積極,假設沒有任何控點一律不會包含任何帳號代碼。
  • 這項功能如何與一般資料類型互動 (如果採用)?舉例來說,如果我們定義了 Pair<A, B> 類型,那麼如果 AB 是資源類型,則該類型應該是資源類型,而不是單獨為 Pair 註解。在其他情況下,更傾向判斷型別是否為資源嗎?

既有藝術與參考資料

本提案的目標是在變更類型的值/資源狀態時,允許發生來源破壞性變更。由於 RFC-0024 已建立 FIDL 的來源相容性標準,因此與這個目標有關。此外,我們還探討了處理方針,導致您難以在 Rust 中使用 Clone 特徵,而這個提案解決了這個問題。

我們未發現其他處理序間通訊 (IPC) 系統正解決這項問題,也就是區分可能包含帳號代碼或系統資源的類型。不過,在程式設計語言中,為類型加上註解的概念就是「感染」所有使用網站的方式。例如,JavaScript、Python 和 Rust 中的非同步函式會有這個行為,而 Haskell 中的 IO 單字也很有作用。


  1. 此提案的較早版本,稱為關鍵字 entity

  2. 此提案的較舊版本需要變更才能進行。