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 系統呼叫,但這可能會失敗)。
  • 協定體系的 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 宣告上允許 resource (RFC-0052)。不過,資源新類型包裝值類型並沒有實際用途,因此新類型會從包裝的類型隱含繼承值/資源狀態。

文法

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

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

JSON IR

這項提案會在 "struct_declarations""table_declarations""union_declarations" 陣列中的所有物件中,新增一個具有布林值的鍵 "resource"

請注意,此鍵與 "max_handles" 並非重複。值類型必須將 max_handles 設為零,但資源類型可以有任意數量的 max_handles,因為這會反映宣告的實際內容 (而非程式庫作者允許句柄的意圖)。

繫結

這項提案不包含繫結的特定變更。不過,這可讓 FIDL 繫結作者 (包括 FIDL 團隊) 解決「動機」一節中討論的問題。以下是這個 FTP 可實現的功能,但接受時並非必要:

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

API 評量標準

API 評分標準應提供使用 resource 的時間點指南。以下列舉一些簡單的案例:

  • 沒有資源類型的結構體不應標示為 resource,因為結構體並未設計為可擴充 (在日後新增句柄,在大多數情況下會破壞 ABI)。
  • 沒有資源類型的嚴格表格或聯集不應標示為 resource,因為嚴格性已表示修改其欄位會造成來源變更。

它也應處理最初沒有句柄的彈性資料表和聯集。舉例來說,我們可能會根據程式庫的用途、程式庫的使用率、使用語言中造成來源變更的預期成本,以及其他因素,建議您選擇某個版本。

導入策略

高階實作步驟如下:

  • 在 fidlc 中剖析 resource 關鍵字。
  • 將現有的 FIDL 程式庫遷移至 resource (詳情請參閱「未知」)。
  • 使用測試驗證 fidlc 中的值/資源類型規則。
  • resource 標記儲存在 JSON IR 中,並在 fidlgen 中公開。

人體工學

這項提案會引入新概念,因此會使 FIDL 變得更加複雜。與其他 FIDL 結構體 (例如「結構體」和「通訊協定」) 不同,新使用者不太可能猜到「資源」的意思,因此需要從說明文件中學習。

這項提案是否讓 FIDL 語言更符合人體工學,仍有待商榷。這有助於吸引開發人員注意含有句柄的宣告,尤其是當實際句柄值隱藏在巢狀結構中時。任何瀏覽程式庫的使用者都會立即看到結構體會攜帶句柄,而非只有資料。另一方面,如果要擔心是否要使用 resource 並輸入關鍵字,可能會讓使用者感到不便。將一個宣告從值變更為資源,可能會產生連鎖效應,導致許多類型都必須變成資源 (雖然這可能會帶來好處,因為否則會顯示為來源損壞)。

由於 FIDL 繫結功能有所改善,因此增加的複雜性是合理的。您可以自由為值類型和資源類型提供不同的 API,讓繫結更安全、更符合人體工學。如需這些改善項目的範例,請參閱「繫結」。

說明文件和範例

您必須完成下列工作:

  • 更新所有涉及句柄的文件,以便視需要使用 resource
  • 更新 FIDL 語言規格,說明 resource 修飾符。
  • 在 FIDL 教學課程中提及 resource。應有簡短的註解說明所有輔助鍵 (即 strictresource)。
  • 提供指引,說明不含句柄的新類型是否應為資源。
  • 繫結充分利用值/資源的差異後,請更新相關文件,指出值類型和資源類型提供的 API 之間的差異,並提供 (如有可能) 兩者之間的轉換操作說明。

回溯相容性

這項提案不會影響 ABI 相容性。

修訂 (2021 年 7 月)。在實作期間,我們發現此提案與 RFC-0033:處理不明欄位和嚴格性的互動中,出現了邊緣情況。部分繫結會在解碼資料表和彈性聯集時儲存不明成員;如果不明成員包含句柄,則無法為值類型執行此操作,因此在這種情況下,解碼作業一定會失敗。詳情請參閱相容性指南

修訂 (2021 年 10 月)。RFC-0137:在 Fidl 中捨棄不明資料 後,繫結項目就不會再儲存不明資料,因此不會再有邊緣情況。因此,值/資源的區別不會影響 ABI 相容性。

新增或移除 resource 修飾符既不相容於來源,也無法轉換2RFC-0024 的定義上。繫結可明確允許為兩個型別產生不相容的 API,這兩個型別的差異僅在修飾符存在與否,實際上,在新增/移除修飾符之前和之後編譯的程式碼可能無法相容。如要以來源相容的方式從 resource 轉換,資料庫作者必須建立新的類型和方法,而非變更現有類型和方法。

一旦繫結作者開始善用值/資源區別,我們就會重新考慮這項決定。要求可轉換的路徑 (例如使用具有 [Transitional] 屬性的中間階段) 可能會很有幫助。一開始,這項功能的限制不明確:可能會過於嚴格,破壞這項提案要啟用的潛在 API 改善功能。

成效

這項提案對建構效能影響不大:FIDL 編譯器會稍微增加一些工作,以便剖析新關鍵字並驗證其用途。這不會對 IPC 效能造成直接影響。如果繫結使用值/資源區別來建立 API,並且不鼓勵不必要的複製作業,則某些語言可能會略為改善。舉例來說,您不必複製值類型物件,即可多次傳送該物件。

安全性

這項提案不會直接影響安全性,但可讓繫結提供更安全的 API。舉例來說,C++ 可以針對資源類型,使用 [[nodiscard]] 強制處理 Clone() 的錯誤,或是透過移動來使用資源類型方法引數,以免之後誤用經過變異的物件。這類變更可避免錯誤,包括安全性錯誤。

測試

我們會透過以下方式測試這項功能:

  • 在 fidlc 中新增測試,針對剖析和驗證程式碼路徑進行測試。這些測試應執行各種情況,其中標示為 resource (或非) 的宣告無法符合資源類型 (或值類型) 的定義。
  • 除了修正需要 resource 的現有宣告外,也請在金本中加入一些資源類型宣告。
  • 更新 fidl-changes 測試套件,以示範從值類型轉換至資源類型,以及從資源類型轉換至值類型的步驟。

缺點、替代方案和未知事項

這項提案會引入新的關鍵字,導致語言變得更複雜。關鍵字過多可能會造成問題;「嚴格的資源聯合」這個詞有點難懂。

本提案以兩種方式削弱 FIDL 可調整性保證:

  • 在此之前,在類型中新增句柄並不會造成來源中斷的變更。這項行為現在已明確允許,且預期會發生 (除非類型已標示為 resource,以便在需要新增句柄時使用)。
  • 在此之前,您可以宣告類型,並預期未來會 (1) 為其新增句柄,以及 (2) 將其納入任何其他類型的欄位。因此,程式庫作者必須一開始就選擇 (1) 或 (2)。

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

  • 不採取任何行動。允許在任何地方使用句柄,並接受在新增或移除句柄時,繫結必須保留來源相容性的事實。
  • 預設允許句柄。與這項提案類似,但假設宣告是預設資源類型,且需要 value 關鍵字才能禁止宣告中的資源類型。

「動機」和「人體工學」部分則說明不採取任何行動的缺點。至於其他替代方案,經驗證明大多數訊息不含句柄,因此在通訊協定中傳遞句柄時,必須謹慎處理並事先規劃。換句話說,值類型是常見的情況,而新增句柄的功能可能不如想像中實用。這表示不允許使用句柄是更好的預設值。

這項提案主要可讓使用 FIDL 繫結的最終開發人員受惠,但其缺點則適用於設計 API 的程式庫作者。這項權衡符合 Fuchsia API 委員會章程,該章程將終端開發人員列為優先,而非 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. 這項提案的舊版要求變更必須可轉換。