Miri is an interpreter for Rust that detects undefined behavior (UB) by running your code in a controlled environment rather than on native hardware. You can run it directly on your project using cargo miri to catch issues like out-of-bounds memory access, data races, and invalid pointer dereferencing before they cause crashes or security vulnerabilities in production.
First, ensure Miri is installed and up to date by running rustup component add miri and cargo miri setup. Once configured, you can run your tests or binaries with cargo miri test or cargo miri run. Miri will execute your code and halt immediately if it encounters undefined behavior, providing a detailed stack trace pointing to the exact line of code causing the issue.
For example, if you have a function that performs unsafe pointer arithmetic, Miri will catch it:
fn unsafe_example() {
let x = [1, 2, 3];
let ptr = x.as_ptr();
// This is undefined behavior: reading out of bounds
let val = unsafe { *ptr.add(10) };
println!("{}", val);
}
fn main() {
unsafe_example();
}
Running cargo miri run on this code will output an error similar to:
error: Undefined Behavior: out of bounds pointer arithmetic
--> src/main.rs:6:22
|
6 | let val = unsafe { *ptr.add(10) };
| ^^^^^^^^^^^^^^^ out of bounds pointer arithmetic
Miri also supports detecting data races in multithreaded code. If you have a shared mutable reference accessed from multiple threads without proper synchronization, Miri will flag it as a data race. For instance:
use std::thread;
fn main() {
let x = Box::new(0);
let x1 = Box::leak(x);
let x2 = x1;
let t1 = thread::spawn(move || {
*x1 += 1;
});
let t2 = thread::spawn(move || {
*x2 += 1;
});
t1.join().unwrap();
t2.join().unwrap();
}
Running cargo miri run here will report a data race because both threads access the same memory location without synchronization.
Keep in mind that Miri runs significantly slower than native execution because it simulates the entire memory model. It is best used during development and testing phases rather than in production builds. Additionally, Miri cannot detect all forms of undefined behavior, particularly those involving external libraries or platform-specific behaviors, but it covers the vast majority of common Rust UB cases.
To integrate Miri into your CI pipeline, you can add a step that runs cargo miri test after your standard tests. This ensures that any new UB introduced in your codebase is caught early.