Debugging 克隆具有未初始化成员的结构后,向量为空
在Rust 1.29.0中,我的一个测试开始失败。我设法把这个奇怪的错误归结到这个例子:Debugging 克隆具有未初始化成员的结构后,向量为空,debugging,rust,undefined-behavior,Debugging,Rust,Undefined Behavior,在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:
#[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());
}
}
#[派生(克隆、调试)]
结构倒数自我{
自我{
值:如果self.has_值>0{
self.value.clone()
}否则{
不安全{std::mem::uninitialized()}
},
有价值:自我有价值,
}
}
}
为MayContainerValue执行impl Drop{
fn下降(&mut自我){
如果self.has_值>0{
不安全{
std::mem::ManuallyDrop::drop(&mut self.value);
}
}
}
}
#[cfg(测试)]
模试验{
使用超级::*;
#[测试]
fn检查滴落物(){
设n=2000;
让drops=std::cell::RefCell::new(0usize);
让mut slots=Vec::new();
对于0..n中的uu{
slots.push(推送)值{
值:std::mem::ManuallyDrop::new(CountDrop(&drops)),
has_值:1,
});
}
不安全{std::mem::ManuallyDrop::drop(&mut slot[0].value);}
插槽[0]。具有_值=0;
断言(slots.len(),slots.clone().len());
}
}
我知道代码看起来很奇怪;这完全是断章取义。我在Rust 1.29.0上的64位Ubuntu上通过货物测试
重现了这个问题。朋友无法在相同版本的Windows上复制
其他阻止繁殖的因素:
- 将
降至900以下n
- 未从
货物测试中运行示例
- 将
的成员替换为CountDrop
u64
- 使用1.29.0之前的锈蚀版本
MayContainValue
可以有一个未初始化的成员,但这永远不会以任何方式使用
我也设法在电脑上重现了这一点
我对涉及使用
选项
或枚举
以某种安全方式重新设计mayContainerValue
的“解决方案”不感兴趣,我使用手动存储和占用/空闲区分是有充分理由的 Rust 1.29.0更改了ManuallyDrop
的定义。过去它是一个联合体
(只有一个成员),但现在它是一个结构和一个lang项。lang项在编译器中的作用是强制类型不具有析构函数,即使它包装了一个具有一次析构函数的类型
我尝试复制ManuallyDrop
的旧定义(这需要每晚,除非添加了T:Copy
绑定),并使用它来代替std
中的定义,它避免了这个问题(至少在网络上)。我还尝试删除第二个插槽(slots[1]
),而不是第一个插槽(slots[0]
)
虽然我无法在我的系统(运行Arch Linux x86_64)上以本机方式重现该问题,但我通过使用以下工具发现了一些有趣的东西:
francis@francis-arch/data/git/miri master
$MIRI_SYSROOT=~/.xargo/HOST cargo run--/data/src/rust/so-manually-drop-1_29/src/main.rs
在0.03秒内完成开发[未优化+调试信息]目标
运行'target/debug/miri/data/src/rust/so-manually-drop-1_29/src/main.rs`
错误[E0080]:常量计算错误:试图读取未定义的字节
-->/home/francis/.rustup/toolschains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/vec.rs:1903:32
|
1903 |对于迭代器中的元素{
|^^^^^^^^^^试图读取未定义的字节
|
注意:对“::spec\u extend”的内部调用`
-->/home/francis/.rustup/toolschains/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())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
注意:对`TL的内部调用;DR:是的,创建未初始化的引用始终是未定义的行为。您不能将mem::uninitialized
安全地用于泛型。目前对于您的特定情况没有一个好的解决方法
在valgrind中运行代码会报告3个错误,每个错误都具有相同的堆栈跟踪:
==741==条件跳转或移动取决于未初始化的值
==741==at 0x11907F:to kick in.这算作None
,它提前终止迭代,导致第二个向量为空
事实证明,拥有mem::uninitialized
(一段给你类似C的语义的代码)是一个巨大的步兵,而且经常被误用(令人惊讶!),因此你在这里并不孤单。作为替代者,你应该遵循的主要事项是:
println!("{}", std::mem::size_of::<Option<std::mem::ManuallyDrop<CountDrop<'static>>>>());
// prints 16 in Rust 1.28, but 8 in Rust 1.29
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,
};
}