aboutsummaryrefslogtreecommitdiff
path: root/rust/qemu-api
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2024-12-11 11:38:20 +0100
committerPaolo Bonzini <pbonzini@redhat.com>2025-01-10 23:34:43 +0100
commitca0d60a6ad777ab617cbc4e6f328eaff60617b3f (patch)
treecf2734268009ecb6b4faeee812cd8e05aae9472c /rust/qemu-api
parent1d03e9771e05685e11bbd3cc8cdd072c02cf580d (diff)
downloadqemu-ca0d60a6ad777ab617cbc4e6f328eaff60617b3f.zip
qemu-ca0d60a6ad777ab617cbc4e6f328eaff60617b3f.tar.gz
qemu-ca0d60a6ad777ab617cbc4e6f328eaff60617b3f.tar.bz2
rust: qom: add ParentField
Add a type that, together with the C function object_deinit, ensures the correct drop order for QOM objects relative to their superclasses. Right now it is not possible to implement the Drop trait for QOM classes that are defined in Rust, as the drop() function would not be called when the object goes away; instead what is called is ObjectImpl::INSTANCE_FINALIZE. It would be nice for INSTANCE_FINALIZE to just drop the object, but this has a problem: suppose you have pub struct MySuperclass { parent: DeviceState, field: Box<MyData>, ... } impl Drop for MySuperclass { ... } pub struct MySubclass { parent: MySuperclass, ... } and an instance_finalize implementation that is like unsafe extern "C" fn drop_object<T: ObjectImpl>(obj: *mut Object) { unsafe { std::ptr::drop_in_place(obj.cast::<T>()) } } When instance_finalize is called for MySubclass, it will walk the struct's list of fields and call the drop method for MySuperclass. Then, object_deinit recurses to the superclass and calls the same drop method again. This will cause double-freeing of the Box<Data>. What's happening here is that QOM wants to control the drop order of MySuperclass and MySubclass's fields. To do so, the parent field must be marked ManuallyDrop<>, which is quite ugly. Instead, add a wrapper type ParentField<> that is specific to QOM. This hides the implementation detail of *what* is special about the ParentField, and will also be easy to check in the #[derive(Object)] macro. Reviewed-by: Zhao Liu <zhao1.liu@intel.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'rust/qemu-api')
-rw-r--r--rust/qemu-api/src/qom.rs64
-rw-r--r--rust/qemu-api/tests/tests.rs4
2 files changed, 60 insertions, 8 deletions
diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs
index 7d5fbef..40d17a9 100644
--- a/rust/qemu-api/src/qom.rs
+++ b/rust/qemu-api/src/qom.rs
@@ -55,6 +55,7 @@
use std::{
ffi::CStr,
+ fmt,
ops::{Deref, DerefMut},
os::raw::c_void,
};
@@ -105,6 +106,52 @@ macro_rules! qom_isa {
};
}
+/// This is the same as [`ManuallyDrop<T>`](std::mem::ManuallyDrop), though
+/// it hides the standard methods of `ManuallyDrop`.
+///
+/// The first field of an `ObjectType` must be of type `ParentField<T>`.
+/// (Technically, this is only necessary if there is at least one Rust
+/// superclass in the hierarchy). This is to ensure that the parent field is
+/// dropped after the subclass; this drop order is enforced by the C
+/// `object_deinit` function.
+///
+/// # Examples
+///
+/// ```ignore
+/// #[repr(C)]
+/// #[derive(qemu_api_macros::Object)]
+/// pub struct MyDevice {
+/// parent: ParentField<DeviceState>,
+/// ...
+/// }
+/// ```
+#[derive(Debug)]
+#[repr(transparent)]
+pub struct ParentField<T: ObjectType>(std::mem::ManuallyDrop<T>);
+
+impl<T: ObjectType> Deref for ParentField<T> {
+ type Target = T;
+
+ #[inline(always)]
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<T: ObjectType> DerefMut for ParentField<T> {
+ #[inline(always)]
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl<T: fmt::Display + ObjectType> fmt::Display for ParentField<T> {
+ #[inline(always)]
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ self.0.fmt(f)
+ }
+}
+
unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut Object) {
// SAFETY: obj is an instance of T, since rust_instance_init<T>
// is called from QOM core as the instance_init function
@@ -151,11 +198,16 @@ unsafe extern "C" fn rust_class_init<T: ObjectType + ClassInitImpl<T::Class>>(
///
/// - the struct must be `#[repr(C)]`;
///
-/// - the first field of the struct must be of the instance struct corresponding
-/// to the superclass, which is `ObjectImpl::ParentType`
+/// - the first field of the struct must be of type
+/// [`ParentField<T>`](ParentField), where `T` is the parent type
+/// [`ObjectImpl::ParentType`]
+///
+/// - the first field of the `Class` must be of the class struct corresponding
+/// to the superclass, which is `ObjectImpl::ParentType::Class`. `ParentField`
+/// is not needed here.
///
-/// - likewise, the first field of the `Class` must be of the class struct
-/// corresponding to the superclass, which is `ObjectImpl::ParentType::Class`.
+/// In both cases, having a separate class type is not necessary if the subclass
+/// does not add any field.
pub unsafe trait ObjectType: Sized {
/// The QOM class object corresponding to this struct. This is used
/// to automatically generate a `class_init` method.
@@ -384,8 +436,8 @@ impl<T: ObjectType> ObjectCastMut for &mut T {}
/// Trait a type must implement to be registered with QEMU.
pub trait ObjectImpl: ObjectType + ClassInitImpl<Self::Class> {
- /// The parent of the type. This should match the first field of
- /// the struct that implements `ObjectImpl`:
+ /// The parent of the type. This should match the first field of the
+ /// struct that implements `ObjectImpl`, minus the `ParentField<_>` wrapper.
type ParentType: ObjectType;
/// Whether the object can be instantiated
diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs
index 1d2825b..526c3f4 100644
--- a/rust/qemu-api/tests/tests.rs
+++ b/rust/qemu-api/tests/tests.rs
@@ -15,7 +15,7 @@ use qemu_api::{
declare_properties, define_property,
prelude::*,
qdev::{DeviceImpl, DeviceState, Property},
- qom::ObjectImpl,
+ qom::{ObjectImpl, ParentField},
vmstate::VMStateDescription,
zeroable::Zeroable,
};
@@ -31,7 +31,7 @@ pub static VMSTATE: VMStateDescription = VMStateDescription {
#[repr(C)]
#[derive(qemu_api_macros::Object)]
pub struct DummyState {
- parent: DeviceState,
+ parent: ParentField<DeviceState>,
migrate_clock: bool,
}