orl*_*rlp 11 debugging undefined-behavior rust
在Rust 1.29.0中,我的一个测试开始失败.我设法将这个奇怪的错误归结为这个例子:
#[derive(Clone, Debug)]
struct CountDrop<'a>(&'a std::cell::RefCell<usize>);
struct MayContainValue<T> {
value: std::mem::ManuallyDrop<T>,
has_value: u32,
}
impl<T: Clone> Clone for MayContainValue<T> {
fn clone(&self) -> Self {
Self {
value: if self.has_value > 0 {
self.value.clone()
} else {
unsafe { std::mem::uninitialized() }
},
has_value: self.has_value,
}
}
}
impl<T> Drop for MayContainValue<T> {
fn drop(&mut self) {
if self.has_value > 0 {
unsafe {
std::mem::ManuallyDrop::drop(&mut self.value);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_drops() {
let n = 2000;
let drops = std::cell::RefCell::new(0usize);
let mut slots = Vec::new();
for _ in 0..n {
slots.push(MayContainValue {
value: std::mem::ManuallyDrop::new(CountDrop(&drops)),
has_value: 1,
});
}
unsafe { std::mem::ManuallyDrop::drop(&mut slots[0].value); }
slots[0].has_value = 0;
assert_eq!(slots.len(), slots.clone().len());
}
}
Run Code Online (Sandbox Code Playgroud)
我知道代码看起来很奇怪; 它完全脱离了背景.我cargo test在Rust 1.29.0上的64位Ubuntu上重现了这个问题.朋友无法在Windows上使用相同的Rust版本重现.
其他阻止再现的事情:
n低于〜900.cargo test.CountDrop用u64.替换成员.这里发生了什么?是的,MayContainValue可以有一个未初始化的成员,但这绝不会以任何方式使用.
我还设法在play.rust-lang.org上重现了这一点.
我对"解决方案"不感兴趣,这些"解决方案"涉及MayContainValue以某种安全的方式重新设计,Option或者enum,我正在使用手动存储和占用/空置歧视这是有充分理由的.
TL; DR:是的,创建未初始化的引用始终是未定义的行为.你不能mem::uninitialized安全地使用泛型.对于您的具体案例,目前没有一个好的解决方法.
在valgrind中运行代码报告3个错误,每个错误具有相同的堆栈跟踪:
==741== Conditional jump or move depends on uninitialised value(s)
==741== at 0x11907F: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T, I>>::spec_extend (vec.rs:1892)
==741== by 0x11861C: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<&'a T, I>>::spec_extend (vec.rs:1942)
==741== by 0x11895C: <alloc::vec::Vec<T>>::extend_from_slice (vec.rs:1396)
==741== by 0x11C1A2: alloc::slice::hack::to_vec (slice.rs:168)
==741== by 0x11C643: alloc::slice::<impl [T]>::to_vec (slice.rs:369)
==741== by 0x118C1E: <alloc::vec::Vec<T> as core::clone::Clone>::clone (vec.rs:1676)
==741== by 0x11AF89: md::tests::check_drops (main.rs:51)
==741== by 0x119D39: md::__test::TESTS::{{closure}} (main.rs:36)
==741== by 0x11935D: core::ops::function::FnOnce::call_once (function.rs:223)
==741== by 0x11F09E: {{closure}} (lib.rs:1451)
==741== by 0x11F09E: call_once<closure,()> (function.rs:223)
==741== by 0x11F09E: <F as alloc::boxed::FnBox<A>>::call_box (boxed.rs:642)
==741== by 0x17B469: __rust_maybe_catch_panic (lib.rs:105)
==741== by 0x14044F: try<(),std::panic::AssertUnwindSafe<alloc::boxed::Box<FnBox<()>>>> (panicking.rs:289)
==741== by 0x14044F: catch_unwind<std::panic::AssertUnwindSafe<alloc::boxed::Box<FnBox<()>>>,()> (panic.rs:392)
==741== by 0x14044F: {{closure}} (lib.rs:1406)
==741== by 0x14044F: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:136)
Run Code Online (Sandbox Code Playgroud)
在保持Valgrind错误(或一个非常相似)的同时减少导致
use std::{iter, mem};
fn main() {
let a = unsafe { mem::uninitialized::<&()>() };
let mut b = iter::once(a);
let c = b.next();
let _d = match c {
Some(_) => 1,
None => 2,
};
}
Run Code Online (Sandbox Code Playgroud)
在操场上的Miri运行这个较小的复制品会导致此错误:
error[E0080]: constant evaluation error: attempted to read undefined bytes
--> src/main.rs:7:20
|
7 | let _d = match c {
| ^ attempted to read undefined bytes
|
note: inside call to `main`
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:74:34
|
74| lang_start_internal(&move || main().report(), argc, argv)
| ^^^^^^
note: inside call to `closure`
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:59:75
|
59| ::sys_common::backtrace::__rust_begin_short_backtrace(move || main())
| ^^^^^^
note: inside call to `closure`
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/sys_common/backtrace.rs:136:5
|
13| f()
| ^^^
note: inside call to `std::sys_common::backtrace::__rust_begin_short_backtrace::<[closure@DefId(1/1:1823 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>`
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:59:13
|
59| ::sys_common::backtrace::__rust_begin_short_backtrace(move || main())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `closure`
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:310:40
|
31| ptr::write(&mut (*data).r, f());
| ^^^
note: inside call to `std::panicking::try::do_call::<[closure@DefId(1/1:1822 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>`
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:306:5
|
30| / fn do_call<F: FnOnce() -> R, R>(data: *mut u8) {
30| | unsafe {
30| | let data = data as *mut Data<F, R>;
30| | let f = ptr::read(&mut (*data).f);
31| | ptr::write(&mut (*data).r, f());
31| | }
31| | }
| |_____^
note: inside call to `std::panicking::try::<i32, [closure@DefId(1/1:1822 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe]>`
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:392:9
|
39| panicking::try(f)
| ^^^^^^^^^^^^^^^^^
note: inside call to `std::panic::catch_unwind::<[closure@DefId(1/1:1822 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>`
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:58:25
|
58| let exit_code = panic::catch_unwind(|| {
| _________________________^
59| | ::sys_common::backtrace::__rust_begin_short_backtrace(move || main())
60| | });
| |__________^
note: inside call to `std::rt::lang_start_internal`
--> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:74:5
|
74| lang_start_internal(&move || main().report(), argc, argv)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
简短版本是mem::uninitialized创建一个空指针,它被视为一个引用.这是未定义的行为.
在原始代码中,Vec::clone通过遍历迭代器来实现.Iterator::next返回一个Option<T>,所以你有一个引用选项,这会导致空指针优化启动.这计为a None,它会提前终止迭代,从而产生空的第二个向量.
事实证明,拥有mem::uninitialized一段代码可以提供类似C语义的代码,它是一个巨大的步枪,经常被误用(惊喜!),所以你并不孤单.作为替代品你应该遵循的主要事项是:
Rust 1.29.0更改了定义ManuallyDrop.它曾经是一个union(有一个成员),但现在它是一个struct和一个郎项目.lang项在编译器中的作用是强制类型不具有析构函数,即使它包装了一次具有析构函数的类型.
我尝试复制旧的定义ManuallyDrop(除非T: Copy添加了绑定,否则每晚需要)并使用它而不是来自std它,并且它避免了问题(至少在Playground上).我也尝试删除第二个插槽(slots[1])而不是第一个(slots[0]),这也恰好起作用.
虽然我无法在我的系统上本机重现问题(运行Arch Linux x86_64),但我发现使用miri有趣的事情:
francis@francis-arch /data/git/miri master
$ MIRI_SYSROOT=~/.xargo/HOST cargo run -- /data/src/rust/so-manually-drop-1_29/src/main.rs
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/miri /data/src/rust/so-manually-drop-1_29/src/main.rs`
error[E0080]: constant evaluation error: attempted to read undefined bytes
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/vec.rs:1903:32
|
1903 | for element in iterator {
| ^^^^^^^^ attempted to read undefined bytes
|
note: inside call to `<std::vec::Vec<T> as std::vec::SpecExtend<T, I>><MayContainValue<CountDrop>, std::iter::Cloned<std::slice::Iter<MayContainValue<CountDrop>>>>::spec_extend`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/vec.rs:1953:9
|
1953 | self.spec_extend(iterator.cloned())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `<std::vec::Vec<T> as std::vec::SpecExtend<&'a T, I>><MayContainValue<CountDrop>, std::slice::Iter<MayContainValue<CountDrop>>>::spec_extend`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/vec.rs:1402:9
|
1402 | self.spec_extend(other.iter())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `<std::vec::Vec<T>><MayContainValue<CountDrop>>::extend_from_slice`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/slice.rs:168:9
|
168 | vector.extend_from_slice(s);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `std::slice::hack::to_vec::<MayContainValue<CountDrop>>`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/slice.rs:369:9
|
369 | hack::to_vec(self)
| ^^^^^^^^^^^^^^^^^^
note: inside call to `std::slice::<impl [T]><MayContainValue<CountDrop>>::to_vec`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/vec.rs:1687:9
|
1687 | <[T]>::to_vec(&**self)
| ^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `<std::vec::Vec<T> as std::clone::Clone><MayContainValue<CountDrop>>::clone`
--> /data/src/rust/so-manually-drop-1_29/src/main.rs:54:33
|
54 | assert_eq!(slots.len(), slots.clone().len());
| ^^^^^^^^^^^^^
note: inside call to `tests::check_drops`
--> /data/src/rust/so-manually-drop-1_29/src/main.rs:33:5
|
33 | tests::check_drops();
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0080`.
Run Code Online (Sandbox Code Playgroud)
(注意:我可以在不使用Xargo的情况下获得相同的错误,但是后来miri没有显示std中堆栈帧的源代码.)
如果我使用原始定义再次执行此操作ManuallyDrop,则miri不会报告任何问题.这证实了新定义ManuallyDrop导致程序具有未定义的行为.
当我换std::mem::uninitialized()到时std::mem::zeroed(),我可以可靠地重现这个问题.当本地运行,如果它发生了未初始化的内存为全零,那么你会得到的问题,否则你不会.
通过调用std::mem::zeroed(),我已经使程序生成了空引用,这些引用被记录为Rust中的未定义行为.克隆向量时,使用迭代器(如上面miri的输出所示).Iterator::next返回Option<T>; 即T在这里中有一个参考(来自何处CountDrops),这会导致Option的存储器布局被优化:代替具有离散的判别式,它使用了一个空引用来表示其None值.由于我正在生成空引用,迭代器返回None的第一个项目,因此矢量结束了空.
有趣的是,当ManuallyDrop被定义为联合时,Option内存布局没有得到优化.
println!("{}", std::mem::size_of::<Option<std::mem::ManuallyDrop<CountDrop<'static>>>>());
// prints 16 in Rust 1.28, but 8 in Rust 1.29
Run Code Online (Sandbox Code Playgroud)
在#52898中有关于这种情况的讨论.
| 归档时间: |
|
| 查看次数: |
214 次 |
| 最近记录: |