Debugging 克隆具有未初始化成员的结构后,向量为空

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:

在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());
    }
}
#[派生(克隆、调试)]
结构倒数自我{
自我{
值:如果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上复制

其他阻止繁殖的因素:

  • n
    降至900以下
  • 未从
    货物测试中运行示例
  • 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,
    };
}