Miri
神経質に笑う このunsafeなやつ、めちゃくちゃ簡単じゃないか。どうしてみんなが違うことを言うのかわからない。私たちのプログラムは完璧に動いている。
ナレーター: 🙂
…そうだよね?
ナレーター: 🙂
さて、私たちは今やunsafeコードを書いているので、コンパイラは以前ほどミスを見つける手助けをしてくれない。テストはたまたま動いただけで、実際には非決定的なことをしていたのかもしれない。未定義動作っぽい何かを。
でも、何ができるというのだろう? 私たちは窓をこじ開けて、rustcの教室から抜け出してしまった。もう誰も助けてはくれない。
…待って、路地にいるあの怪しげな人は誰?
「おい坊や、Rustコードをインタプリトしてみないか?」
えっ、いや? どうして、
「ワイルドだぜ。プログラムの実際の動的実行がRustのメモリモデルのセマンティクスに従っているかを検証できるんだ。ぶっ飛ぶぞ…」
何?
「未定義動作をしているかどうかをチェックするんだ。」
インタプリタを一度だけ試してみてもいいかもしれない。
「rustupはインストールしてあるよな?」
もちろん。最新のRustツールチェーンを使うためのあのツールだからね!
> rustup +nightly-2022-01-21 component add miri
info: syncing channel updates for 'nightly-2022-01-21-x86_64-pc-windows-msvc'
info: latest update on 2022-01-21, rust version 1.60.0-nightly (777bb86bc 2022-01-20)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
info: installing component 'rust-std'
info: installing component 'rustc'
info: installing component 'rustfmt'
info: downloading component 'miri'
info: installing component 'miri'
今、私のコンピュータに何をインストールしたの!?
「良いブツだ」
ナレーター: ツールチェーンのバージョンについて、少し妙なことが起きています:
今インストールしているツールである
miriは、rustcの内部と密接に連携するため、nightlyツールチェーンでしか利用できません。
+nightly-2022-01-21は、その日付のRust nightlyツールチェーンでmiriをインストールしたい、ということをrustupに伝えます。特定の日付を指定しているのは、miriが遅れて数日分のnightlyではビルドできないことがあるためです。指定したツールチェーンがまだインストールされていなければ、rustupは+で指定したツールチェーンを自動的にダウンロードします。2022-01-21は、miriのサポートがあると私が知っているnightlyというだけです。それはこのステータスページで確認できます。 運が良いと思うなら、単に
+nightlyを使っても構いません。
cargo miri経由でmiriを呼び出すときも、miriをインストールしたツールチェーンを指定するために、この+構文を使います。毎回指定したくない場合は、rustup override setを使えます。
> cargo +nightly-2022-01-21 miri test
I will run `"cargo.exe" "install" "xargo"` to install
a recent enough xargo. Proceed? [Y/n]
えっ、一体全体XARGOって何?
「大丈夫、気にするな。」
> y
Updating crates.io index
Installing xargo v0.3.24
...
Finished release [optimized] target(s) in 10.65s
Installing C:\Users\ninte\.cargo\bin\xargo-check.exe
Installing C:\Users\ninte\.cargo\bin\xargo.exe
Installed package `xargo v0.3.24` (executables `xargo-check.exe`, `xargo.exe`)
I will run `"rustup" "component" "add" "rust-src"` to install
the `rust-src` component for the selected toolchain. Proceed? [Y/n]
えっ???
「Rustのソースコードのコピーを持つのが嫌いな人なんている?」
> y
info: downloading component 'rust-src'
info: installing component 'rust-src'
「おお、準備できたぞ。ここからがいいところだ。」
Compiling lists v0.1.0 (C:\Users\ninte\dev\tmp\lists)
Finished test [unoptimized + debuginfo] target(s) in 0.25s
Running unittests (lists-5cc11d9ee5c3e924.exe)
error: Undefined Behavior: trying to reborrow for Unique at alloc84055,
but parent tag <209678> does not have an appropriate item in
the borrow stack
--> \lib\rustlib\src\rust\library\core\src\option.rs:846:18
|
846 | Some(x) => Some(f(x)),
| ^ trying to reborrow for Unique at alloc84055,
| but parent tag <209678> does not have an
| appropriate item in the borrow stack
|
= help: this indicates a potential bug in the program:
it performed an invalid operation, but the rules it
violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md
for further information
= note: inside `std::option::Option::<std::boxed::Box<fifth::Node<i32>>>::map::<i32, [closure@src\fifth.rs:31:30: 40:10]>` at \lib\rustlib\src\rust\library\core\src\option.rs:846:18
note: inside `fifth::List::<i32>::pop` at src\fifth.rs:31:9
--> src\fifth.rs:31:9
|
31 | / self.head.take().map(|head| {
32 | | let head = *head;
33 | | self.head = head.next;
34 | |
... |
39 | | head.elem
40 | | })
| |__________^
note: inside `fifth::test::basics` at src\fifth.rs:74:20
--> src\fifth.rs:74:20
|
74 | assert_eq!(list.pop(), Some(1));
| ^^^^^^^^^^
note: inside closure at src\fifth.rs:62:5
--> src\fifth.rs:62:5
|
61 | #[test]
| ------- in this procedural macro expansion
62 | / fn basics() {
63 | | let mut list = List::new();
64 | |
65 | | // Check empty list behaves right
... |
96 | | assert_eq!(list.pop(), None);
97 | | }
| |_____^
...
error: aborting due to previous error
うわ。とんでもないエラーだ。
「だろう、このヤバいやつを見ろよ。こういうのは見ていて最高だ。」
ありがとう?
「ほら、エストラジオールの瓶も持っていけ。後で必要になる。」
待って、なんで?
「これからメモリモデルについて考えることになるんだ。信じろ。」
ナレーター: その謎の人物はその後、狐に変身し、壁の穴を抜けて走り去っていきました。著者はそれから数分間、今起きたすべての出来事を理解しようとして、中空を見つめていました。
路地裏の謎の狐は、私のジェンダー以外についても正しかった。miriは本当に最高のブツだ。
では、miriとは何なのだろう?
Rust の中間レベル中間表現(MIR)用の実験的なインタープリターです。cargo プロジェクトのバイナリやテストスイートを実行し、たとえば次のような特定の種類の未定義動作を検出できます。
- 境界外メモリアクセスと use-after-free
- 初期化されていないデータの不正な使用
- intrinsic の事前条件違反(unreachable_unchecked に到達する、copy_nonoverlapping を重複する範囲で呼び出す、など)
- 十分にアラインされていないメモリアクセスと参照
- いくつかの基本的な型不変条件の違反(たとえば、0 または 1 ではない bool、あるいは不正な enum 判別子)
- 実験的: 参照型のエイリアシングを管理する Stacked Borrows ルールの違反
- 実験的: データ競合(ただし弱いメモリ効果は対象外)
さらに、Miri はメモリリークについても知らせてくれます。実行終了時にまだ割り当てられたままのメモリがあり、そのメモリがグローバル static から到達可能でない場合、Miri はエラーを発生させます。
…
ただし、Miri はプログラム内の未定義動作をすべて検出できるわけではなく、すべてのプログラムを実行できるわけでもないことに注意してください
TL;DR: これはあなたのプログラムを解釈し、実行時にルールを破って未定義動作をやらかしたかどうかを検知します。未定義動作は一般に実行時に発生するものなので、これは必要です。もしその問題がコンパイル時に見つけられるなら、コンパイラが単にそれをエラーにするはずです!
ubsan や tsan のようなツールに馴染みがあるなら、基本的にはそれらを全部まとめて、さらに過激にしたものです。
Miri は今、教室の窓の外にナイフを持ってぶら下がっています。学習用ナイフです。
Miri に私たちの作業をチェックしてもらいたくなったら、次のようにテストスイートを解釈するよう頼めます。
> cargo +nightly-2022-01-21 miri test
では、彼らが私たちの机に彫り込んだ内容をもう少し詳しく見てみましょう。
error: Undefined Behavior: trying to reborrow for Unique at alloc84055, but parent tag <209678> does not have an appropriate item in the borrow stack
--> \lib\rustlib\src\rust\library\core\src\option.rs:846:18
|
846 | Some(x) => Some(f(x)),
| ^ trying to reborrow for Unique at alloc84055,
| but parent tag <209678> does not have an
| appropriate item in the borrow stack
|
= help: this indicates a potential bug in the program: it
performed an invalid operation, but the rules it
violated are still experimental
= help: see
https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md
for further information
まあ、私たちがエラーを起こしたことはわかりますが、これはわかりにくいエラーメッセージです。「borrow stack」とは何でしょうか?
次のセクションでそれを解明してみます。