5.4 Miri

原文链接: https://rust-unofficial.github.io/too-many-lists/fifth-miri.html

紧张地笑 这些 unsafe 东西太容易了,我不知道为什么大家都说不然。我们的程序完美运行。

旁白: 🙂

……对吧?

旁白: 🙂

嗯,我们现在在写 unsafe 代码,所以编译器不能像以前那样帮我们抓错。测试碰巧能通过,但实际上可能在做非确定性的事。某种未定义行为的事。

但我们能怎么办?我们撬开窗户,溜出了 rustc 的教室。没人能帮我们了。

……等等,巷子里那个鬼鬼祟祟的人是谁?

“嘿小子,想解释一些 Rust 代码吗?”

什——不?为什么,

“太狂野了兄弟,它能验证程序的实际动态执行是否符合 Rust 内存模型的语义。震撼你的心灵……”

什么?

“它检查你是否做了未定义行为。”

我想我可以试一次解释器。

“你装了 rustup 对吧?”

当然装了,这是保持 Rust 工具链最新的那个工具!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
> 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 告诉 rustup 我们要用那个日期的 rust nightly 工具链安装 miri。我指定具体日期是因为有时 miri 会落后,几个 nightly 都编不过。rustup 会用 + 指定的工具链自动下载我们还没有的。

2022-01-21 只是我知道支持 miri 的一个 nightly,可以在这个状态页查看。如果你运气好可以直接用 +nightly。

每当我们通过 cargo miri 调用 miri 时,也会用这个 + 语法指定安装 miri 的工具链。如果不想每次都指定,可以用 rustup override set。

1
2
3
4
> cargo +nightly-2022-01-21 miri test

I will run `"cargo.exe" "install" "xargo"` to install
a recent enough xargo. Proceed? [Y/n]

呃 XARGO 到底是什么?

“没事,别担心。”

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
> 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 源代码的副本?”

1
2
3
4
> y

info: downloading component 'rust-src'
info: installing component 'rust-src'

“噢耶准备好了,精彩部分来了。”

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
   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 是什么?

An experimental interpreter for Rust’s mid-level intermediate representation (MIR). It can run binaries and test suites of cargo projects and detect certain classes of undefined behavior, for example:

  • Out-of-bounds memory accesses and use-after-free
  • Invalid use of uninitialized data
  • Violation of intrinsic preconditions (an unreachable_unchecked being reached, calling copy_nonoverlapping with overlapping ranges, …)
  • Not sufficiently aligned memory accesses and references
  • Violation of some basic type invariants (a bool that is not 0 or 1, for example, or an invalid enum discriminant)
  • Experimental: Violations of the Stacked Borrows rules governing aliasing for reference types
  • Experimental: Data races (but no weak memory effects)

On top of that, Miri will also tell you about memory leaks: when there is memory still allocated at the end of the execution, and that memory is not reachable from a global static, Miri will raise an error.

…

However, be aware that Miri will not catch all cases of undefined behavior in your program, and cannot run all programs

TL;DR:它解释你的程序,注意你是否在运行时打破了规则并做了未定义行为。这是必要的,因为未定义行为通常发生在运行时。如果问题能在编译期发现,编译器会直接报错!

如果你熟悉 ubsan 和 tsan:基本上就是那些但合在一起且更极端。


Miri 现在拿着刀趴在教室窗外。一把学习用的刀。

如果我们想让 miri 检查我们的工作,可以请他们这样解释我们的测试套件:

1
> cargo +nightly-2022-01-21 miri test

现在仔细看看他们在我们课桌上刻了什么:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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"是什么?

我们下一节试着搞清楚。

最后修改 August 23, 2026: 更新 (499855b16)