From f50cd85c8475c16374d0e138efda222ce4455f53 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 19 Dec 2024 14:32:16 +0100 Subject: rust: qom: add casting functionality Add traits that let client cast typecast safely between object types. In particular, an upcast is compile-time guaranteed to succeed, and a YOLO C-style downcast must be marked as unsafe. The traits are based on an IsA<> trait that declares what is a subclass of what, which is an idea taken from glib-rs (https://docs.rs/glib/latest/glib/object/trait.IsA.html). The four primitives are also taken from there (https://docs.rs/glib/latest/glib/object/trait.Cast.html). However, the implementation of casting itself is a bit different and uses the Deref trait. This removes some pointer arithmetic from the pl011 device; it is also a prerequisite for the definition of methods, so that they can be invoked on all subclass structs. This will use the IsA<> trait to detect the structs that support the methods. glib also has a "monadic" casting trait which could be implemented on Option (as in https://docs.rs/glib/latest/glib/object/trait.CastNone.html) and perhaps even Result. For now I'm leaving it out, as the patch is already big enough and the benefit seems debatable. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api/tests/tests.rs | 68 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) (limited to 'rust/qemu-api/tests/tests.rs') diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index dc0ed19019..7b63e28c2f 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -2,7 +2,11 @@ // Author(s): Manos Pitsidianakis // SPDX-License-Identifier: GPL-2.0-or-later -use std::ffi::CStr; +use std::{ + ffi::CStr, + os::raw::c_void, + ptr::{addr_of, addr_of_mut}, +}; use qemu_api::{ bindings::*, @@ -31,6 +35,8 @@ pub struct DummyState { migrate_clock: bool, } +qom_isa!(DummyState: Object, DeviceState); + declare_properties! { DUMMY_PROPERTIES, define_property!( @@ -81,3 +87,63 @@ fn test_object_new() { object_unref(object_new(DummyState::TYPE_NAME.as_ptr()).cast()); } } + +// a note on all "cast" tests: usually, especially for downcasts the desired +// class would be placed on the right, for example: +// +// let sbd_ref = p.dynamic_cast::(); +// +// Here I am doing the opposite to check that the resulting type is correct. + +#[test] +#[allow(clippy::shadow_unrelated)] +/// Test casts on shared references. +fn test_cast() { + init_qom(); + let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; + + let p_ref: &DummyState = unsafe { &*p }; + let obj_ref: &Object = p_ref.upcast(); + assert_eq!(addr_of!(*obj_ref), p.cast()); + + let sbd_ref: Option<&SysBusDevice> = obj_ref.dynamic_cast(); + assert!(sbd_ref.is_none()); + + let dev_ref: Option<&DeviceState> = obj_ref.downcast(); + assert_eq!(addr_of!(*dev_ref.unwrap()), p.cast()); + + // SAFETY: the cast is wrong, but the value is only used for comparison + unsafe { + let sbd_ref: &SysBusDevice = obj_ref.unsafe_cast(); + assert_eq!(addr_of!(*sbd_ref), p.cast()); + + object_unref(p_ref.as_object_mut_ptr().cast::()); + } +} + +#[test] +#[allow(clippy::shadow_unrelated)] +/// Test casts on mutable references. +fn test_cast_mut() { + init_qom(); + let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; + + let p_ref: &mut DummyState = unsafe { &mut *p }; + let obj_ref: &mut Object = p_ref.upcast_mut(); + assert_eq!(addr_of_mut!(*obj_ref), p.cast()); + + let sbd_ref: Result<&mut SysBusDevice, &mut Object> = obj_ref.dynamic_cast_mut(); + let obj_ref = sbd_ref.unwrap_err(); + + let dev_ref: Result<&mut DeviceState, &mut Object> = obj_ref.downcast_mut(); + let dev_ref = dev_ref.unwrap(); + assert_eq!(addr_of_mut!(*dev_ref), p.cast()); + + // SAFETY: the cast is wrong, but the value is only used for comparison + unsafe { + let sbd_ref: &mut SysBusDevice = obj_ref.unsafe_cast_mut(); + assert_eq!(addr_of_mut!(*sbd_ref), p.cast()); + + object_unref(p_ref.as_object_mut_ptr().cast::()); + } +} -- cgit 1.4.1