rust: qom: change instance_init to take a ParentInit<>

This removes undefined behavior associated to writing to uninitialized
fields, and makes it possible to remove "unsafe" from the instance_init
implementation.

However, the init function itself is still unsafe, because it must promise
(as a sort as MaybeUninit::assume_init) that all fields have been
initialized.

Reviewed-by: Zhao Liu <zhao1.liu@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Paolo Bonzini 2025-03-04 20:48:05 +01:00
parent 8d394f6cf0
commit 345bef46a1
5 changed files with 63 additions and 57 deletions

View file

@ -2,7 +2,7 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
use std::{ffi::CStr, mem::size_of, ptr::addr_of_mut}; use std::{ffi::CStr, mem::size_of};
use qemu_api::{ use qemu_api::{
chardev::{CharBackend, Chardev, Event}, chardev::{CharBackend, Chardev, Event},
@ -11,9 +11,10 @@ use qemu_api::{
memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder},
prelude::*, prelude::*,
qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl}, qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl},
qom::{ObjectImpl, Owned, ParentField}, qom::{ObjectImpl, Owned, ParentField, ParentInit},
static_assert, static_assert,
sysbus::{SysBusDevice, SysBusDeviceImpl}, sysbus::{SysBusDevice, SysBusDeviceImpl},
uninit_field_mut,
vmstate::VMStateDescription, vmstate::VMStateDescription,
}; };
@ -163,7 +164,7 @@ impl PL011Impl for PL011State {
impl ObjectImpl for PL011State { impl ObjectImpl for PL011State {
type ParentType = SysBusDevice; type ParentType = SysBusDevice;
const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = Some(Self::init); const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = Some(Self::init);
const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init); const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init);
const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>; const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>;
} }
@ -488,7 +489,7 @@ impl PL011State {
/// `PL011State` type. It must not be called more than once on the same /// `PL011State` type. It must not be called more than once on the same
/// location/instance. All its fields are expected to hold uninitialized /// location/instance. All its fields are expected to hold uninitialized
/// values with the sole exception of `parent_obj`. /// values with the sole exception of `parent_obj`.
unsafe fn init(&mut self) { unsafe fn init(mut this: ParentInit<Self>) {
static PL011_OPS: MemoryRegionOps<PL011State> = MemoryRegionOpsBuilder::<PL011State>::new() static PL011_OPS: MemoryRegionOps<PL011State> = MemoryRegionOpsBuilder::<PL011State>::new()
.read(&PL011State::read) .read(&PL011State::read)
.write(&PL011State::write) .write(&PL011State::write)
@ -496,28 +497,23 @@ impl PL011State {
.impl_sizes(4, 4) .impl_sizes(4, 4)
.build(); .build();
// SAFETY: // SAFETY: this and this.iomem are guaranteed to be valid at this point
//
// self and self.iomem are guaranteed to be valid at this point since callers
// must make sure the `self` reference is valid.
MemoryRegion::init_io( MemoryRegion::init_io(
unsafe { &mut *addr_of_mut!(self.iomem) }, &mut uninit_field_mut!(*this, iomem),
addr_of_mut!(*self),
&PL011_OPS, &PL011_OPS,
"pl011", "pl011",
0x1000, 0x1000,
); );
self.regs = Default::default(); uninit_field_mut!(*this, regs).write(Default::default());
// SAFETY: let clock = DeviceState::init_clock_in(
// &mut this,
// self.clock is not initialized at this point; but since `Owned<_>` is "clk",
// not Drop, we can overwrite the undefined value without side effects; &Self::clock_update,
// it's not sound but, because for all PL011State instances are created ClockEvent::ClockUpdate,
// by QOM code which calls this function to initialize the fields, at );
// leastno code is able to access an invalid self.clock value. uninit_field_mut!(*this, clock).write(clock);
self.clock = self.init_clock_in("clk", &Self::clock_update, ClockEvent::ClockUpdate);
} }
const fn clock_update(&self, _event: ClockEvent) { const fn clock_update(&self, _event: ClockEvent) {

View file

@ -21,8 +21,8 @@ use qemu_api::{
hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED, hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED,
}, },
prelude::*, prelude::*,
qdev::{DeviceImpl, DeviceMethods, DeviceState, Property, ResetType, ResettablePhasesImpl}, qdev::{DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl},
qom::{ObjectImpl, ObjectType, ParentField}, qom::{ObjectImpl, ObjectType, ParentField, ParentInit},
qom_isa, qom_isa,
sysbus::{SysBusDevice, SysBusDeviceImpl}, sysbus::{SysBusDevice, SysBusDeviceImpl},
timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND}, timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND},
@ -697,7 +697,7 @@ impl HPETState {
.set(self.counter.get().deposit(shift, len, val)); .set(self.counter.get().deposit(shift, len, val));
} }
unsafe fn init(&mut self) { unsafe fn init(mut this: ParentInit<Self>) {
static HPET_RAM_OPS: MemoryRegionOps<HPETState> = static HPET_RAM_OPS: MemoryRegionOps<HPETState> =
MemoryRegionOpsBuilder::<HPETState>::new() MemoryRegionOpsBuilder::<HPETState>::new()
.read(&HPETState::read) .read(&HPETState::read)
@ -707,18 +707,14 @@ impl HPETState {
.impl_sizes(4, 8) .impl_sizes(4, 8)
.build(); .build();
// SAFETY:
// self and self.iomem are guaranteed to be valid at this point since callers
// must make sure the `self` reference is valid.
MemoryRegion::init_io( MemoryRegion::init_io(
unsafe { &mut *addr_of_mut!(self.iomem) }, &mut uninit_field_mut!(*this, iomem),
addr_of_mut!(*self),
&HPET_RAM_OPS, &HPET_RAM_OPS,
"hpet", "hpet",
HPET_REG_SPACE_LEN, HPET_REG_SPACE_LEN,
); );
Self::init_timers(unsafe { &mut *((self as *mut Self).cast::<MaybeUninit<Self>>()) }); Self::init_timers(&mut this);
} }
fn post_init(&self) { fn post_init(&self) {
@ -900,7 +896,7 @@ unsafe impl ObjectType for HPETState {
impl ObjectImpl for HPETState { impl ObjectImpl for HPETState {
type ParentType = SysBusDevice; type ParentType = SysBusDevice;
const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = Some(Self::init); const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = Some(Self::init);
const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init); const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init);
const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>; const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>;
} }

View file

@ -16,6 +16,7 @@ use crate::{
callbacks::FnCall, callbacks::FnCall,
cell::Opaque, cell::Opaque,
prelude::*, prelude::*,
uninit::MaybeUninitField,
zeroable::Zeroable, zeroable::Zeroable,
}; };
@ -147,7 +148,7 @@ impl MemoryRegion {
#[inline(always)] #[inline(always)]
unsafe fn do_init_io( unsafe fn do_init_io(
slot: *mut bindings::MemoryRegion, slot: *mut bindings::MemoryRegion,
owner: *mut Object, owner: *mut bindings::Object,
ops: &'static bindings::MemoryRegionOps, ops: &'static bindings::MemoryRegionOps,
name: &'static str, name: &'static str,
size: u64, size: u64,
@ -156,7 +157,7 @@ impl MemoryRegion {
let cstr = CString::new(name).unwrap(); let cstr = CString::new(name).unwrap();
memory_region_init_io( memory_region_init_io(
slot, slot,
owner.cast::<bindings::Object>(), owner,
ops, ops,
owner.cast::<c_void>(), owner.cast::<c_void>(),
cstr.as_ptr(), cstr.as_ptr(),
@ -166,16 +167,15 @@ impl MemoryRegion {
} }
pub fn init_io<T: IsA<Object>>( pub fn init_io<T: IsA<Object>>(
&mut self, this: &mut MaybeUninitField<'_, T, Self>,
owner: *mut T,
ops: &'static MemoryRegionOps<T>, ops: &'static MemoryRegionOps<T>,
name: &'static str, name: &'static str,
size: u64, size: u64,
) { ) {
unsafe { unsafe {
Self::do_init_io( Self::do_init_io(
self.0.as_mut_ptr(), this.as_mut_ptr().cast(),
owner.cast::<Object>(), MaybeUninitField::parent_mut(this).cast(),
&ops.0, &ops.0,
name, name,
size, size,

View file

@ -19,7 +19,7 @@ use crate::{
error::{Error, Result}, error::{Error, Result},
irq::InterruptSource, irq::InterruptSource,
prelude::*, prelude::*,
qom::{ObjectClass, ObjectImpl, Owned}, qom::{ObjectClass, ObjectImpl, Owned, ParentInit},
vmstate::VMStateDescription, vmstate::VMStateDescription,
}; };
@ -247,15 +247,9 @@ unsafe impl ObjectType for DeviceState {
} }
qom_isa!(DeviceState: Object); qom_isa!(DeviceState: Object);
/// Trait for methods exposed by the [`DeviceState`] class. The methods can be /// Initialization methods take a [`ParentInit`] and can be called as
/// called on all objects that have the trait `IsA<DeviceState>`. /// associated functions.
/// impl DeviceState {
/// The trait should only be used through the blanket implementation,
/// which guarantees safety via `IsA`.
pub trait DeviceMethods: ObjectDeref
where
Self::Target: IsA<DeviceState>,
{
/// Add an input clock named `name`. Invoke the callback with /// Add an input clock named `name`. Invoke the callback with
/// `self` as the first parameter for the events that are requested. /// `self` as the first parameter for the events that are requested.
/// ///
@ -266,12 +260,15 @@ where
/// which Rust code has a reference to a child object) it would be /// which Rust code has a reference to a child object) it would be
/// possible for this function to return a `&Clock` too. /// possible for this function to return a `&Clock` too.
#[inline] #[inline]
fn init_clock_in<F: for<'a> FnCall<(&'a Self::Target, ClockEvent)>>( pub fn init_clock_in<T: DeviceImpl, F: for<'a> FnCall<(&'a T, ClockEvent)>>(
&self, this: &mut ParentInit<T>,
name: &str, name: &str,
_cb: &F, _cb: &F,
events: ClockEvent, events: ClockEvent,
) -> Owned<Clock> { ) -> Owned<Clock>
where
T::ParentType: IsA<DeviceState>,
{
fn do_init_clock_in( fn do_init_clock_in(
dev: &DeviceState, dev: &DeviceState,
name: &str, name: &str,
@ -287,10 +284,10 @@ where
unsafe { unsafe {
let cstr = CString::new(name).unwrap(); let cstr = CString::new(name).unwrap();
let clk = bindings::qdev_init_clock_in( let clk = bindings::qdev_init_clock_in(
dev.as_mut_ptr(), dev.0.as_mut_ptr(),
cstr.as_ptr(), cstr.as_ptr(),
cb, cb,
dev.as_void_ptr(), dev.0.as_void_ptr(),
events.0, events.0,
); );
@ -307,12 +304,12 @@ where
// SAFETY: the opaque is "this", which is indeed a pointer to T // SAFETY: the opaque is "this", which is indeed a pointer to T
F::call((unsafe { &*(opaque.cast::<T>()) }, event)) F::call((unsafe { &*(opaque.cast::<T>()) }, event))
} }
Some(rust_clock_cb::<Self::Target, F>) Some(rust_clock_cb::<T, F>)
} else { } else {
None None
}; };
do_init_clock_in(self.upcast(), name, cb, events) do_init_clock_in(unsafe { this.upcast_mut() }, name, cb, events)
} }
/// Add an output clock named `name`. /// Add an output clock named `name`.
@ -324,16 +321,30 @@ where
/// which Rust code has a reference to a child object) it would be /// which Rust code has a reference to a child object) it would be
/// possible for this function to return a `&Clock` too. /// possible for this function to return a `&Clock` too.
#[inline] #[inline]
fn init_clock_out(&self, name: &str) -> Owned<Clock> { pub fn init_clock_out<T: DeviceImpl>(this: &mut ParentInit<T>, name: &str) -> Owned<Clock>
where
T::ParentType: IsA<DeviceState>,
{
unsafe { unsafe {
let cstr = CString::new(name).unwrap(); let cstr = CString::new(name).unwrap();
let clk = bindings::qdev_init_clock_out(self.upcast().as_mut_ptr(), cstr.as_ptr()); let dev: &mut DeviceState = this.upcast_mut();
let clk = bindings::qdev_init_clock_out(dev.0.as_mut_ptr(), cstr.as_ptr());
let clk: &Clock = Clock::from_raw(clk); let clk: &Clock = Clock::from_raw(clk);
Owned::from(clk) Owned::from(clk)
} }
} }
}
/// Trait for methods exposed by the [`DeviceState`] class. The methods can be
/// called on all objects that have the trait `IsA<DeviceState>`.
///
/// The trait should only be used through the blanket implementation,
/// which guarantees safety via `IsA`.
pub trait DeviceMethods: ObjectDeref
where
Self::Target: IsA<DeviceState>,
{
fn prop_set_chr(&self, propname: &str, chr: &Owned<Chardev>) { fn prop_set_chr(&self, propname: &str, chr: &Owned<Chardev>) {
assert!(bql_locked()); assert!(bql_locked());
let c_propname = CString::new(propname).unwrap(); let c_propname = CString::new(propname).unwrap();

View file

@ -382,12 +382,15 @@ impl<T> DerefMut for ParentInit<'_, T> {
} }
unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut bindings::Object) { unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut bindings::Object) {
let mut state = NonNull::new(obj).unwrap().cast::<T>(); let mut state = NonNull::new(obj).unwrap().cast::<MaybeUninit<T>>();
// SAFETY: obj is an instance of T, since rust_instance_init<T> // SAFETY: obj is an instance of T, since rust_instance_init<T>
// is called from QOM core as the instance_init function // is called from QOM core as the instance_init function
// for class T // for class T
unsafe { unsafe {
T::INSTANCE_INIT.unwrap()(state.as_mut()); ParentInit::with(state.as_mut(), |parent_init| {
T::INSTANCE_INIT.unwrap()(parent_init);
});
} }
} }
@ -654,7 +657,7 @@ pub trait ObjectImpl: ObjectType + IsA<Object> {
/// ///
/// FIXME: The argument is not really a valid reference. `&mut /// FIXME: The argument is not really a valid reference. `&mut
/// MaybeUninit<Self>` would be a better description. /// MaybeUninit<Self>` would be a better description.
const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = None; const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = None;
/// Function that is called to finish initialization of an object, once /// Function that is called to finish initialization of an object, once
/// `INSTANCE_INIT` functions have been called. /// `INSTANCE_INIT` functions have been called.