RFC-0051:更安全的 C++ 結構

RFC-0051:適用於 C++ 的安全結構體
狀態已遭拒
區域
  • FIDL
說明

允許 C++ 開發人員編寫 FIDL 程式碼,如果結構體未完全初始化,編譯時就會中斷。

作者
提交日期 (年-月-日)2018-07-19
審查日期 (年-月-日)2019-03-14

拒絕原因

這項 RFC 最初於 2019 年 3 月 14 日獲准。不過,該實作從未合併,且 C++ 繫結自此發生重大變化。因此,我們追溯性拒絕這項 RFC。

摘要

允許 C++ 開發人員編寫 FIDL 程式碼,如果結構體未完全初始化,編譯時就會中斷。

提振精神

在 Peridot 中,我們有複雜的 FIDL 結構體,隨著我們更瞭解如何解決所面臨的問題,這些結構體也會隨之改變。這些結構體通常會深層巢狀化,並在建構位置以外的程式碼中傳送。在疊代結構體時,我們經常會對語意進行重大變更,新增必要欄位或將先前選填的欄位設為必要欄位。很難追蹤所有需要更新的程式碼。這些錯誤不會顯示為編譯時間錯誤,而是執行階段錯誤,因此很難與錯誤初始化結構體的程式碼建立關聯。

在進行變更之前,Dart 程式碼中普遍存在相同類別的問題,變更後,所有必要欄位都必須傳遞至結構體建構函式。這項變更大幅提升了 Dart 程式碼的開發效率和穩定性。

設計

這會修改 C++ 繫結程式庫和程式碼產生器。這項功能不會移除任何現有介面,只會新增建構 FIDL 結構體執行個體的新方法。

這會為 FIDL 結構體新增建構工具模式。 使用方式如下:

FooPtr foo = Foo::Builder()->set_bar("hello")->set_baz("world");

結構體類別的 Builder() 靜態方法會傳回範本化建構工具物件。建構工具範本參數會擷取要建構的結構類型,以及結構中每個未設定欄位的類別。其中包含結構體的例項。

欄位類別有兩個方法:set_name(value) 方法會在例項上設定欄位值,並傳回製作工具,其中欄位已從製作工具的範本引數中移除;Check() 方法則會針對選用欄位執行空值作業,並針對必要欄位執行 static_assert 失敗作業。

建構工具類別會擴充範本參數中的所有欄位型別,讓開發人員存取 setter 方法。開發人員呼叫設定器並接收新的建構工具型別時,建構工具範本引數中的欄位類別清單會縮減。舉例來說,省略部分範本惡作劇:

Foo::Builder() 是具有 set_bar()set_baz() 方法的 Builder<Foo, Foo::Field_bar, Foo::Field_baz>

Foo::Builder()->set_bar(...) 是具有 set_baz() 方法的 Builder<Foo, Foo::Field_baz>

Foo::Builder()->set_bar(...)->set_baz(...)Builder<Foo> ,沒有任何 setter 方法。

建構工具具有隱含轉換運算子,可轉換為結構體型別和結構體指標型別。這些方法會對其餘欄位型別呼叫 Check() 方法,並傳回建構工具持有的結構體例項。Check() 方法會是無運算 (適用於選填欄位),或是static_assert失敗,並指定尚未設定的必填欄位。

說明文件和範例

FIDL 教學課程和範例將更新,說明建立結構體執行個體的傳統和新方式。

回溯相容性

這項提案純粹是增補性質。 不會有回溯不相容的問題。

效能

這項變更不會產生任何執行階段效能成本。這項功能已在 Compiler Explorer 中完成原型設計,確保不會產生或執行額外程式碼。

這會在繫結程式庫中新增標頭檔案,並在產生的 C++ 程式碼中,為每個結構體欄位新增幾行程式碼。C++ 編譯器必須多做一點工作才能解析範本,但不會在編譯中新增任何會造成重大影響的額外步驟。

安全性

這項變更可讓我們將程式設計師的錯誤從執行階段錯誤轉為建構時間錯誤。這會縮減程式的狀態空間,並減少必須正確處理及測試的錯誤案例數量。 減少非預期行為有助於提升安全性。

測試

C++ 繫結單元測試應擴充,以測試建構工具是否正確設定不同類型的欄位。

測試編譯器是否會偵測到建構工具的錯誤用法 (即未設定必要欄位) 相當困難。我們不清楚該如何測試。

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

這會將一些相當棘手的範本新增至 FIDL C++ 繫結程式庫。 這會增加維護負擔,且可能造成一些小型的建構時間負擔。

先前的範本方法使用位元遮罩,範本較簡單,但有 64 個必填欄位等限制,且 FIDL 編譯器也較複雜。

我們也可以建構 Linter,嘗試追蹤是否已設定所有必要欄位。這似乎是相當複雜的資料流分析。

既有技術和參考資料

Dart 繫結已於去年變更,因此結構建構函式會為每個欄位採用具名引數。必填欄位會標示為必填,這樣一來,dartanalyzer 就能拒絕導致部分欄位未初始化的變更。