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

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

允許 C++ 開發人員編寫 FIDL 程式碼,在結構體未完全初始化的情況下,會在編譯時間損毀。

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

拒絕原因

2019 年 3 月 14 日一開始接受了這個 RFC。但是,我們從未合併實作,而 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 方法。當開發人員呼叫 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(...) 是不含任何 setter 方法的 Builder<Foo>

建構工具提供結構類型和結構指標類型的隱含轉換運算子。這些類型會對剩餘的欄位類型呼叫 Check() 方法,並傳回建構工具保留的結構執行個體。Check() 方法為免人工管理 (選用欄位) 或 static_assert 失敗,用於指出未設定的必填欄位。

說明文件與範例

我們會更新 FIDL 教學課程和範例,以示範建立結構執行個體的傳統和新方式。

回溯相容性

本提案純屬累加。 無法回溯相容。

效能

這項變更不會產生執行階段效能費用。而是在 Compiler Explorer 中的原型,確保不會產生或執行任何其他程式碼。

該程式碼會將新的標頭檔案新增至繫結程式庫,並在產生的 C++ 程式碼中為每個結構欄位新增幾個額外行。C++ 編譯器需要執行一些額外工作才能解析範本,但不會加入任何額外步驟來編譯可能會嚴重影響的範本。

安全性

這項變更可讓程式設計師將執行階段錯誤從執行階段錯誤轉換成建構時間錯誤。這樣可以減少程式的狀態空間,並減少必須正確處理及測試的錯誤情況數量。這種做法可以減少非預期行為,有利於確保安全。

測試

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

要測試編譯器是否正確使用建構工具 (例如:無法設定必填欄位),並不容易。我們清楚說明瞭該測試方式。

缺點、替代方案和未知

這麼做會新增一些稍微困難的範本至 FIDL C++ 繫結程式庫。這會造成維護負擔,但可能會有一些微幅的建構時間負擔。

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

我們也可以建構 Linter,嘗試追蹤必填欄位是否已設定完成。這似乎是相當複雜的 Dataflow 分析作業。

先前的圖片和參考資料

Dart 繫結已於去年變更,因此結構建構函式會為每個欄位使用已命名的引數。必要項目會標示為必要項目,這樣一來,Dartanalyzer 可以拒絕部分欄位未初始化的變更。