ファジング
このガイドにおける ファジング とは、rustc のバグを見つける目的で、多種多様なプログラムをコンパイルすることを伴う任意のテスト手法を指します。 ファジングは、内部コンパイラエラー(ICE)を見つけるためによく使用されます。 ファジングは、ユーザーが遭遇する前にバグを発見できるため有益です。 また、バグの追跡を容易にする、小さく自己完結したプログラムも提供します。 しかし、よくあるいくつかの誤りによって、ファジングの有用性が低下し、結果としてコントリビューターの負担が増えることがあります。 Rust プロジェクトに対する好影響を最大化するため、ファザーで生成されたバグを報告する前に、このガイドを読んでください!
ガイドライン
要約
やってください:
- バグが最新の nightly rustc でもまだ存在することを確認する
- バグ報告には、合理的に最小化されたスタンドアロンの例を含める
- バグ報告テンプレートで要求されているすべての情報を含める
- 同じメッセージとクエリスタックを持つ既存の報告を検索する
- テストケースを
rustfmtでフォーマットする - そのバグがファジングによって見つかったことを示す
やらないでください:
custom_mir、lang_items、no_core、rustc_attrsなどを含むがこれらに限定されない、内部機能を使用するバグを大量に報告しないでください。- rustc をクラッシュさせることが既知の入力をファザーのシードにしないでください(詳細は後述)。
議論
ある ICE が既に報告済みのものと重複しているかどうか確信が持てない場合は、報告したうえで、関連している可能性があると思う issue にリンクしてください。 一般に、同じ行で発生している ICE でも、クエリスタック が異なる場合は、通常は別個のバグです。 たとえば、#109020 と #109129 には似たエラーメッセージがありました。
error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:195:90: Failed to normalize <[closure@src/main.rs:36:25: 36:28] as std::ops::FnOnce<(Emplacable<()>,)>>::Output, maybe try to call `try_normalize_erasing_regions` instead
error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:195:90: Failed to normalize <() as Project>::Assoc, maybe try to call `try_normalize_erasing_regions` instead
しかし、それらは異なるクエリスタックを持っています。
query stack during panic:
#0 [fn_abi_of_instance] computing call ABI of `<[closure@src/main.rs:36:25: 36:28] as core::ops::function::FnOnce<(Emplacable<()>,)>>::call_once - shim(vtable)`
end of query stack
query stack during panic:
#0 [check_mod_attrs] checking attributes in top-level module
#1 [analysis] running analysis passes on this crate
end of query stack
コーパスの構築
コーパスを構築するときは、rustc をクラッシュさせることが既にわかっているテストを収集しないようにしてください。 そのようなテストをシードにしたファザーは、同じ根本原因を持つバグを生成する可能性が高くなります。 これを避ける最も簡単な方法は、コーパス内の各ファイルをループし、それが ICE を引き起こすかどうかを確認し、引き起こす場合は削除することです。
コーパスを構築するには、以下を使用するとよいでしょう。
- rustc/rust-analyzer/clippy のテストスイート(あるいはソースコード)— ただし、既に失敗を引き起こすことがわかっているテストは避けてください。そうしたテストは、しばしば
//@ failure-status: 101や//@ known-bug: #NNNのようなコメントで始まります。 - アーカイブされた Glacier リポジトリ内の、既に修正済みの ICE — ただし、
ices/内の未修正のものは避けてください!
追加でできること
ICE を報告した後に Rust プロジェクトを支援するためにできることがいくつかあります。
- バグがいつ導入されたかを把握するために、バグを bisect する。
リグレッションを引き起こした PR / コミットを見つけた場合は、その issue に
S-has-bisectionラベルを付けることができます。 見つからない場合は、代わりにE-needs-bisectionを適用することを検討してください。 - 「気を散らす要素」を修正する: 構文エラーや borrow-checking エラーなど、ICE の発生に寄与しないテストケース上の問題
- テストケースを最小化する(下記参照)。
成功した場合は、その issue に
S-has-mcveラベルを付けることができます。 そうでなければ、E-needs-mcveを適用できます。 - 最小化されたテストケースを crash test として rust-lang/rust リポジトリに追加する。
その際、自分の PR に他の「未追跡」のクラッシュを含めることも検討してください。
PR がマージされたら、関連するすべての issue に
S-bug-has-testを付けることを忘れないでください。
ラベルの適用と削除 も参照してください。
最小化
ファザーで生成された入力を注意深く 最小化 することは有用です。 最小化するときは、元のエラーを保持し、構文、型チェック、または borrow-checking エラーなどの気を散らす問題を導入しないように注意してください。
最小化に役立つツールがいくつかあります。
これらのツールを使用している間に、構文、型、borrow-checking エラーを導入しないようにする方法がわからない場合は、完全なテストケースと最小化されたテストケースの両方を投稿してください。
一般に、構文を認識する ツールは、最も短い時間で最良の結果をもたらします。
treereduce-rust と picireny は構文を認識します。
halfempty はそうではありませんが、一般に高品質なツールです。
効果的なファジング
rustc をファジングするときは、機械語コードの生成を避けるとよいでしょう。これは主に LLVM によって行われるためです。
代わりに --emit=mir を試してください。
さまざまなコンパイラフラグによって、異なる問題を発見できます。
-Zmir-opt-level=4 は、デフォルトでは実行されない MIR 最適化パスを有効にし、興味深いバグを発見する可能性があります。
-Zvalidate-mir は、そのようなバグの発見に役立ちます。
自分でビルドしたコンパイラをファジングしている場合は、1 秒あたりの実行回数をもう少し絞り出すために、-C target-cpu=native、または PGO/BOLT でビルドするとよいでしょう。
もちろん、複数のビルド構成を試し、実際にどれがより優れたスループットをもたらすかを確認するのが最善です。
追加のバグを見つけるために、デバッグアサーションを有効にしてソースから rustc をビルドするとよいでしょう。ただし、これにより各実行で追加の作業が必要になり、ファジングが遅くなる可能性があります。
デバッグアサーションを有効にするには、rustc をコンパイルするときに bootstrap.toml に以下を追加します。
rust.debug-assertions = true
再現にデバッグアサーションが必要な ICE には、
requires-debug-assertions タグを付ける必要があります。
既存のプロジェクト
- fuzz-rustc は、libfuzzer で rustc をファジングする方法を示しています
- icemaker は、多数のソースファイルに対してさまざまなフラグで rustc やその他のツールを実行し、ICE を検出します
- tree-splicer は、正しい構文を維持しながら既存のソースファイルを組み合わせることで、新しいソースファイルを生成します