想象一下,你正试图为一些可变的静态对象编写一个安全的 Package 器:
#![feature(strict_provenance)]
#![deny(fuzzy_provenance_casts)]
#![deny(lossy_provenance_casts)]
struct Wrapper<T>(*mut T);
struct Guard<T>(*mut T);
impl<T> Wrapper<T> {
fn guard(&self) -> Guard<T> {
Guard(self.0)
}
}
// imagine there's synchronization going on that makes this all safe
unsafe impl<T: Send + Sync> Sync for Wrapper<T> {}
impl<T> std::ops::Deref for Guard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.0 }
}
}
impl<T> std::ops::DerefMut for Guard<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.0 }
}
}
static mut GLOBAL_SINGLETON: isize = 0; // Imagine this couldn't be trivially replaced by an atomic type
static WRAPPER: Wrapper<isize> = Wrapper(unsafe { &GLOBAL_SINGLETON as *const _ as *mut _ });
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Is this sound in this sepcific instance?
// Actually dereference the raw pointer to provoke UB
fn main() {
assert_eq!(0, *WRAPPER.guard());
*WRAPPER.guard() += 1;
assert_eq!(1, *WRAPPER.guard());
}
现在,通常情况下,强制转换&T as *const T as *mut T
并写入结果原始指针是不合理的,但严格出处和Miri都没有抱怨上述代码。这是否是由于堆叠借用和/或Miri的限制而导致的假阴性?或者static mut
项目以某种方式在全球范围内被标记为SharedRW
,并在这里获得免费通行证?在Stacked Borrows paper中使用Ctrl-F表示“静态”并没有显示任何相关内容。
我也考虑过类似的东西,我更喜欢它,因为它迫使T
比Wrapper
更长寿。
struct Wrapper<'a, T>(&'a std::cell::UnsafeCell<T>);
impl<'a, T> Wrapper<'a, T> {
const fn new(t: &'a T) -> Self {
Self(unsafe { std::mem::transmute(t) }
}
}
// ...
static mut GLOBAL_SINGLETON: isize = 0;
static WRAPPER: Wrapper<'_, isize> = Wrapper::new(&GLOBAL_SINGLETON);
// ...
但这显然是不合理的(Miri会告诉你),因为它要求&GLOBAL_SINGLETON
在&UnsafeCell { GLOBAL_SINGLETON }
活着的时候也活着。
2条答案
按热度按时间ggazkfy81#
1.这是立即未定义的行为,因为除了当T是
UnsafeCell
时,您无法以任何方式从&T
生成&mut T
。你至少应该写这样的东西:MIRI没有捕捉到这个特定的例子,但如果你运行更简单的程序,你可以看到:https://play.integer32.com/?version=stable&mode=debug&edition=2021&gist=b881d107244c95bd37d8fb3221a2b267
MIRI输出:
另请参阅nomicon entry,了解如何将共享引用转换为可变引用。
Wrapper
和Guard
的API虽然本身不会导致未定义的行为,但仍然是不健全。这意味着您可以通过使用它们从安全Rust触发UB。例如,这将是UB:
如果你调用T的任何方法,它都很容易得到,因为它很难跟踪可重入性。如果你把多线程添加到混合中,情况就更糟了。
我建议不要使用可变的静态数据,因为无论如何都很不方便正确地访问它们。
我可以想象只有这个声音API可变静态:
hyrbngr72#
两个现有的答案都指出这是UB,所以我添加了一个我自己的答案,以反映Ralf Jung更细致的输入。如果有人知道,我想就是他了:
这是因为我们在编译时没有一个别名模型,所以没有任何东西会记住这个指针来自一个共享引用。我们还没有决定别名规则是否适用于编译时和运行时之间的边界。他们在这里不是超级有用,我认为因为一切都是全球性的...这是一个UCG问题。1
和/或
它处于未确定/未确定的领域。所以请不要这么做。:)2
因此,这不是绝对的UB,但它也不是绝对不是UB,应该避免等待决定。
Ralf还在UCG存储库中创建了a new issue来解决这个问题,因此请参阅未来的开发。
1:https://github.com/rust-lang/miri/issues/2937#issuecomment-1600365067
2:https://github.com/rust-lang/miri/issues/2937#issuecomment-1602458297