On 3/4/25 10:13, Zhao Liu wrote:
-    const fn as_mut_ptr(&self) -> *mut Self {
-        self as *const Timer as *mut _
+    /// Create a `Timer` struct without initializing it.
+    ///
+    /// # Safety
+    ///
+    /// The timer must be initialized before it is armed with
+    /// [`modify`](Self::modify).
+    pub unsafe fn new() -> Self {
+        // SAFETY: requirements relayed to callers of Timer::new
+        Self(unsafe { Opaque::zeroed() })

We should use Opaque::uninit()? Because 
MaybeUninit::<bindings::QEMUTimer>::zeroed()
marks the timer as initialized, which disables MaybeUninit's ability to check 
for
initialization. e.g.,

While neither is good, a zeroed area of memory behaves better than an uninitialized one... In particular, Drop calls timer_del() which works fine with a zeroed QEMUTimer. With Opaque::uninit() you could have a crash just with

    drop(Timer::new());

// No compiling error or runtime panic
let t: MaybeUninit<bindings::QEMUTimer> = MaybeUninit::zeroed();
let _t = unsafe { t.assume_init() };

Further more, I spent some time trying to figure out if MaybeUninit in
Opaque<> could help identify UB caused by uninitialized Timer, but I found
it doesn't work. :-(

// No compiling error or runtime panic
let mut v: UnsafeCell<MaybeUninit<bindings::QEMUTimer>> = 
UnsafeCell::new(MaybeUninit::uninit());
let _v = unsafe { v.get_mut().assume_init() };

But when I adjust MaybeUninit as the outer wrapper, the UB check can
work:

// Runtime panic: Illegal instruction
let v: MaybeUninit<UnsafeCell<bindings::QEMUTimer>> = MaybeUninit::uninit();
let _v = unsafe { v.assume_init() };

Compared with linux's Opaque, it also puts MaybeUninit on the outermost
layer.

Yes, I admit I just copied what Linux does. :)

And there's another example:

https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html#method.raw_get

Emm, I guess now we have UnsafeCell<MaybeUninit<>> because interior
mutability is expected... but this layout breaks MaybeUninit's functionality.

Thanks for the example from the documentation! Indeed it should be possible to do

    /// Returns a raw mutable pointer to the opaque data.
    pub const fn as_mut_ptr(&self) -> *mut T {
        UnsafeCell::raw_get(self.value.as_ptr())
    }

    /// Returns a raw pointer to the opaque data that can be passed to a
    /// C function as `void *`.
    pub const fn as_void_ptr(&self) -> *mut std::ffi::c_void {
        self.as_mut_ptr().cast()
    }

    pub const fn raw_get(slot: *const Self) -> *mut T {
        // SAFETY: even if uninitialized, slot points to a MaybeUninit
        let slot = slot.cast::<MaybeUninit<UnsafeCell<T>>>;
        UnsafeCell::raw_get(slot.as_ptr())
    }

if Opaque<> uses a MaybeUninit<UnsafeCell<T>>. I'm a bit worried of deviating from what Linux does though...

Paolo


Reply via email to