Caveat of working with raw pointers.
This is a simple example to show how to detect an invalid use of raw pointers. When you try to return a reference to a local value, Rust compiler in most cases will detect it and provide a compite-time error. One exception is when unsafe is used to create a reference from a raw pointer. At this point, the compiler will not be able to verify how long the reference will be valid for.
This is illustrated in this simple example:
use core::mem::MaybeUninit;
use core::slice::from_raw_parts;
const MAX_SIZE: usize = 64;
pub fn upscale(values: &[u8]) -> &[u64] {
let mut returned_array = [MaybeUninit::<u64>::uninit(); MAX_SIZE];
for (placeholder, value) in returned_array.iter_mut().zip(values.iter()) {
placeholder.write(*value as u64);
}
// 🛑 While this compile and run, it is undefined behaviour to return a
// reference to a local value. The `returned_array` will be dropped at
// the end of this function, so the returned reference will point to
// potentially invalid memory.
//
// The compiler is not able to detect this since it is using `unsafe`
// code to create the reference. Running a test through Miri will
// catch this issue.
unsafe { from_raw_parts(returned_array.as_ptr() as *const u64, values.len()) }
}
#[cfg(test)]
mod tests {
#[test]
fn test_upscale() {
let values = [1u8, 2u8, 3u8, 4u8];
let upscaled = super::upscale(&values);
assert_eq!(upscaled, &[1u64, 2u64, 3u64, 4u64]);
}
}This code compiles and runs, giving the impression that it is all fine, but running the test through Miri will reveal the issue:
running 1 test
test tests::test_upscale ... error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free)
--> src/lib.rs:22:24
|
22 | let upscaled = super::upscale(&values);
| ^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (use-after-free)
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `tests::test_upscale`:
= note: inside `tests::test_upscale` at src/lib.rs:22:24: 22:47The error refers to the fact that upscale creates a local array and then gets a reference to it using the unsafe from_raw_parts and returns it. The local array is droped at the end of the function, so any reference to local variables are invalid at this point. Since the test is quite simple, just running the code does not trigger the issue. But a second test using a bit more memory will always fail:
#[test]
fn test_consecutive_upscale() {
let values_1 = [1u8, 2u8, 3u8, 4u8];
let upscaled_1 = super::upscale(&values_1);
let values_2 = [10u8, 20u8, 30u8, 40u8];
let upscaled_2 = super::upscale(&values_2);
assert_eq!(upscaled_1, &[1u64, 2u64, 3u64, 4u64]);
assert_eq!(upscaled_2, &[10u64, 20u64, 30u64, 40u64]);
}This generates the error:
thread 'tests::test_consecutive_upscale' panicked at src/lib.rs:41:9:
assertion `left == right` failed
left: [4329135476, 20, 64, 5082464880]
right: [1, 2, 3, 4]This happens because we used more memory and very likely reused the "local" memory created by the first call to upscale, so the returned upscaled_1 does not reference the expected values at the end.
Building is a simple:
cargo buildRunning the test:
cargo testRunning miri to detect the "use-after-free" issue:
cargo miri testThe code is licensed under the Apache License Version 2.0