This is a problem of potential undefined behavior of uninitialized memory in unsafe rust.
Vec<Vec<usize>>
, or must be vector of something on heap instead of stack; in other words, Vec<usize>
type will not panic here);MaybeUninit
);
then a double free occurs.The code (playground) listed as follows:
#[deny(clippy::uninit_vec)]
unsafe fn uninitialized_vec<T>(size: usize) -> Vec<T> {
let mut v: Vec<T> = Vec::with_capacity(size);
unsafe { v.set_len(size) };
v
}
fn case_1() {
println!("=== Case 1 ===");
let vec_a: Vec<Vec<usize>> = vec![vec![0]; 4];
let _ = vec_a.clone();
let _: Vec<Vec<usize>> = unsafe { uninitialized_vec(4) };
}
fn case_2() {
println!("=== Case 2 ===");
let vec_a: Vec<Vec<usize>> = vec![vec![0]; 4];
{
let _ = vec_a.clone();
}
let _: Vec<Vec<usize>> = unsafe { uninitialized_vec(4) };
}
fn main() {
case_1();
case_2();
}
case_2
will stuck here;case_1
will stuck here.From my common sense, the code itself does not intended to double free any variable. We just declared an uninitialized variable, but not using that variable. If this is not a bug, compiler optimization is probably the only reason that can explain this problem.
What I'm curious is that: whether this code actually triggers undefined behavior in unsafe rust, that compiler optimization may cause double free or other problems. And
Additionally, it is known that by using MaybeUninit
properly may avoid double-free runtime error like this:
use std::mem::MaybeUninit;
unsafe fn uninitialized_vec<T>(size: usize) -> Vec<MaybeUninit<T>> {
let mut v: Vec<MaybeUninit<T>> = Vec::with_capacity(size);
unsafe { v.set_len(size) };
v
}
fn main() {
println!("=== This will not cause error ===");
let vec_a: Vec<Vec<usize>> = vec![vec![0]; 12];
{
let _ = vec_a.clone();
}
let _: Vec<MaybeUninit<Vec<usize>>> = unsafe { uninitialized_vec(12) };
}
Still, MaybeUninit
can be inconvenient in many circumstances. Subjectively, I prefer using Vec<T>
instead of Vec<MaybeUninit<T>>
, especially when I can make sure values of this uninitialized vector will be correctly filled later.
We just declared an uninitialized variable, but not using that variable
This is incorrect. We are using this variable - namely, we are dropping the outer Vec
, therefore dropping all the Vec
s in it, and since they are uninitialized, we're trying to drop arbitrary memory.
whether this code actually triggers undefined behavior in unsafe rust
It does, according to documentation for set_len
:
Safety
<...>
The elements at old_len..new_len must be initialized.
This requirement is explicitly violated.
undefined behavior in unsafe rust, that compiler optimization may cause double free or other problems
If you trigger undefined behavior, compilation process may do to your program anything at all, by definition. It's not "this code may be misoptimized in this way" - it's "you lied to the compiler, all bets are off".