From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 8C376CAC5BB for ; Wed, 1 Oct 2025 08:06:58 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1v3rmZ-0005od-Ei; Wed, 01 Oct 2025 04:01:51 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1v3rmR-0005ku-Sw for qemu-devel@nongnu.org; Wed, 01 Oct 2025 04:01:45 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.129.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1v3rm5-000644-So for qemu-devel@nongnu.org; Wed, 01 Oct 2025 04:01:43 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1759305673; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=FGsO9mQ5Yl0dh5ExsV/Kb/ofPeUjzELu5Bj5e3kYk1k=; b=BmmriA6hNgG4xTw0ChNdT2VvfHYTxJH5mAJ6qd1H/OyooZ/Dbh39VygHP+DDSCT17YAYol q0CKRhELe+KJzWig/8NussUsMfuFbeL1yMMYD2pJyh2M312MDLmkreiJeEW8XhzbyNaIq2 m9l0tFVvgrvxsAutCePB/7X7JabGpzE= Received: from mail-ej1-f69.google.com (mail-ej1-f69.google.com [209.85.218.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-693-O7jqFs4DNhiYuEn8a7qwAA-1; Wed, 01 Oct 2025 04:01:12 -0400 X-MC-Unique: O7jqFs4DNhiYuEn8a7qwAA-1 X-Mimecast-MFC-AGG-ID: O7jqFs4DNhiYuEn8a7qwAA_1759305671 Received: by mail-ej1-f69.google.com with SMTP id a640c23a62f3a-afcb7338319so567347466b.0 for ; Wed, 01 Oct 2025 01:01:11 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1759305669; x=1759910469; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=FGsO9mQ5Yl0dh5ExsV/Kb/ofPeUjzELu5Bj5e3kYk1k=; b=TXTZyL6hVNdo28udMHZub6aDra+Uo5G9QXbNmi/Okt+nns61sxHS7wRGoLslmXpMFX FqQQf+akMe96h5RqPrAk+H8/M1OmO87tws/98SICkVdB8qJc7tVXwXJHze4wPGEjDrTL O1BWGt+YhBgv6C2mRuH3qW8RHKbQq+b8wjKSTIshmkfMkBlgJV5kb7wnAhygpKZov/+P mPyNxyHrDSLaBf737Kruy7X7ef/QtCn1VJuPiuj68p0B8XJ/aTElTM5ka4EcqHLN/+RT 9RcFR0pFWfZA/sUIr+2OE4pjSMSEz5AfYSFBVf9o5cI5HBRpgYjDnVambBX/ioa1KZQx aBbg== X-Gm-Message-State: AOJu0YyBW62l+mcZ56/yMpJ4vQhJhCdrxDZCINTK3xAco5xBIJmChPI1 J9mRNoSKpmahbyOBC5STUB0vF9rhqb2enlAuMRmA2uROHi1cq6gkfgZhgsQqsRYaRNCsygbhz5D KAJ3dzN8AKw5/Us2dea+ow8OomTUGLYnx7Ocr9bQF2BrWiMMPvtlahn3GNM1uTQw+b2m37RFk0J S4dRSwlQPgXUca7Ie4vuzMixPbrUl06XvLu7PEBwX7 X-Gm-Gg: ASbGncsQGHwm2ikdiyqKbxcywEADz0SntkRdT9zEczYWtUHDhSlyG8od84yCOObe1oP BXLh4Ycto4sSChldpVWzodpq8q6fC0+fdpsEZJq187hZK1DjibYDiKYzfDAV+buxxO+dI2RuH0L 7X007A0nbXB4cHF7Znv6qf/g2waPNw5c4My8YUMBUitSkVR1FudHUInrqwigoWbbaVApF49HR+B 47UcD7Wg7TzmUbqCUKz0JTHEmL0PSCN/3jQf4a5fkBSXs3X2F3dkbSklrQJtnOnn+d3kpX/xdAP qahO4BHZsWkZRzl+F2USMS6TEE3FOE82WeFk5MQZG38FQPWnFG97Iu3bMyq50C/MJSuHJc1MGWM Du3GfH85wN13mpCj/r3sKxR8YRLckMXQQK5HLODgSE/sLqP4Z3SY= X-Received: by 2002:a05:6402:5191:b0:634:a546:de45 with SMTP id 4fb4d7f45d1cf-63678ca1dbcmr3301031a12.23.1759305669322; Wed, 01 Oct 2025 01:01:09 -0700 (PDT) X-Google-Smtp-Source: AGHT+IFBdLXa+Wsm8ooCMfh0VtcCWoy5zh+OFyjPlM95jWOXgkHipLXXJjljgyEJ66PWKQHEi6h+fw== X-Received: by 2002:a05:6402:5191:b0:634:a546:de45 with SMTP id 4fb4d7f45d1cf-63678ca1dbcmr3300973a12.23.1759305668643; Wed, 01 Oct 2025 01:01:08 -0700 (PDT) Received: from [192.168.10.48] ([176.206.127.188]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-634a3af53basm11649122a12.41.2025.10.01.01.01.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Oct 2025 01:01:08 -0700 (PDT) From: Paolo Bonzini To: qemu-devel@nongnu.org Cc: qemu-rust@nongnu.org, armbru@redhat.com, marcandre.lureau@redhat.com Subject: [PATCH 07/14] rust: add Deserializer (from_qobject) implementation for QObject Date: Wed, 1 Oct 2025 10:00:44 +0200 Message-ID: <20251001080051.1043944-8-pbonzini@redhat.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251001075005.1041833-1-pbonzini@redhat.com> References: <20251001075005.1041833-1-pbonzini@redhat.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=170.10.129.124; envelope-from=pbonzini@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -24 X-Spam_score: -2.5 X-Spam_bar: -- X-Spam_report: (-2.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_HELO_PASS=-0.001, T_SPF_TEMPERROR=0.01 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org This allows creating any serializable data structure from QObject. Co-authored-by: Marc-André Lureau Signed-off-by: Marc-André Lureau Signed-off-by: Paolo Bonzini --- docs/devel/rust.rst | 1 + rust/util/meson.build | 1 + rust/util/src/qobject/deserializer.rs | 373 ++++++++++++++++++++++++++ rust/util/src/qobject/error.rs | 8 +- rust/util/src/qobject/mod.rs | 2 + 5 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 rust/util/src/qobject/deserializer.rs diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 79c26d9d165..c7f3a496a25 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -162,6 +162,7 @@ module status ``util::error`` stable ``util::log`` proof of concept ``util::module`` complete +``util::qobject`` stable ``util::timer`` stable ========================== ====================== diff --git a/rust/util/meson.build b/rust/util/meson.build index 2b72af99dd5..45366d03786 100644 --- a/rust/util/meson.build +++ b/rust/util/meson.build @@ -39,6 +39,7 @@ _util_rs = static_library( {'.': _util_bindings_inc_rs, 'qobject': [ 'src/qobject/mod.rs', + 'src/qobject/deserializer.rs', 'src/qobject/deserialize.rs', 'src/qobject/error.rs', 'src/qobject/serializer.rs', diff --git a/rust/util/src/qobject/deserializer.rs b/rust/util/src/qobject/deserializer.rs new file mode 100644 index 00000000000..a2fe417e72a --- /dev/null +++ b/rust/util/src/qobject/deserializer.rs @@ -0,0 +1,373 @@ +//! `QObject` deserializer +//! +//! This module implements a [`Deserializer`](serde::de::Deserializer) that +//! produces `QObject`s, allowing them to be turned into deserializable data +//! structures (such as primitive data types, or structs that implement +//! `Deserialize`). + +use std::ffi::CStr; + +use serde::de::{ + self, value::StrDeserializer, DeserializeSeed, Expected, MapAccess, SeqAccess, Unexpected, + Visitor, +}; + +use super::{ + error::{Error, Result}, + match_qobject, QObject, +}; +use crate::bindings; + +impl QObject { + #[cold] + fn invalid_type(&self, exp: &dyn Expected) -> E + where + E: serde::de::Error, + { + serde::de::Error::invalid_type(self.unexpected(), exp) + } + + #[cold] + fn unexpected(&self) -> Unexpected<'_> { + match_qobject! { (self) => + () => Unexpected::Unit, + bool(b) => Unexpected::Bool(b), + i64(n) => Unexpected::Signed(n), + u64(n) => Unexpected::Unsigned(n), + f64(n) => Unexpected::Float(n), + CStr(s) => s.to_str().map_or_else( + |_| Unexpected::Other("string with invalid UTF-8"), + Unexpected::Str), + QList(_) => Unexpected::Seq, + QDict(_) => Unexpected::Map, + } + } +} + +fn visit_qlist_ref<'de, V>(qlist: &'de bindings::QList, visitor: V) -> Result +where + V: Visitor<'de>, +{ + struct QListDeserializer(*mut bindings::QListEntry, usize); + + impl<'de> SeqAccess<'de> for QListDeserializer { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: DeserializeSeed<'de>, + { + if self.0.is_null() { + return Ok(None); + } + + let e = unsafe { &*self.0 }; + // increment the reference count because deserialize consumes `value`. + let value = unsafe { QObject::cloned_from_raw(e.value.cast_const()) }; + let result = seed.deserialize(value); + self.0 = unsafe { e.next.tqe_next }; + self.1 += 1; + result.map(Some) + } + } + + let mut deserializer = QListDeserializer(unsafe { qlist.head.tqh_first }, 0); + let seq = visitor.visit_seq(&mut deserializer)?; + if deserializer.0.is_null() { + Ok(seq) + } else { + Err(serde::de::Error::invalid_length( + deserializer.1, + &"fewer elements in array", + )) + } +} + +fn visit_qdict_ref<'de, V>(qdict: &'de bindings::QDict, visitor: V) -> Result +where + V: Visitor<'de>, +{ + struct QDictDeserializer(*mut bindings::QDict, *const bindings::QDictEntry); + + impl<'de> MapAccess<'de> for QDictDeserializer { + type Error = Error; + + fn next_key_seed(&mut self, seed: T) -> Result> + where + T: DeserializeSeed<'de>, + { + if self.1.is_null() { + return Ok(None); + } + + let e = unsafe { &*self.1 }; + let key = unsafe { CStr::from_ptr(e.key) }; + let key_de = StrDeserializer::new(key.to_str()?); + seed.deserialize(key_de).map(Some) + } + + fn next_value_seed(&mut self, seed: T) -> Result + where + T: DeserializeSeed<'de>, + { + if self.1.is_null() { + panic!("next_key must have returned None"); + } + + let e = unsafe { &*self.1 }; + // increment the reference count because deserialize consumes `value`. + let value = unsafe { QObject::cloned_from_raw(e.value) }; + let result = seed.deserialize(value); + self.1 = unsafe { bindings::qdict_next(self.0, self.1) }; + result + } + } + + let qdict = (qdict as *const bindings::QDict).cast_mut(); + let e = unsafe { &*bindings::qdict_first(qdict) }; + let mut deserializer = QDictDeserializer(qdict, e); + let map = visitor.visit_map(&mut deserializer)?; + if deserializer.1.is_null() { + Ok(map) + } else { + Err(serde::de::Error::invalid_length( + unsafe { bindings::qdict_size(qdict) }, + &"fewer elements in map", + )) + } +} + +fn visit_qnum_ref<'de, V>(qnum: QObject, visitor: V) -> Result +where + V: Visitor<'de>, +{ + match_qobject! { (qnum) => + i64(n) => visitor.visit_i64(n), + u64(n) => visitor.visit_u64(n), + f64(n) => visitor.visit_f64(n), + _ => Err(qnum.invalid_type(&"number")), + } +} + +macro_rules! deserialize_number { + ($method:ident) => { + fn $method(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visit_qnum_ref(self, visitor) + } + }; +} +impl<'de> serde::Deserializer<'de> for QObject { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + () => visitor.visit_unit(), + bool(v) => visitor.visit_bool(v), + i64(n) => visitor.visit_i64(n), + u64(n) => visitor.visit_u64(n), + f64(n) => visitor.visit_f64(n), + CStr(cstr) => visitor.visit_borrowed_str(cstr.to_str()?), + QList(qlist) => visit_qlist_ref(qlist, visitor), + QDict(qdict) => visit_qdict_ref(qdict, visitor), + } + } + + deserialize_number!(deserialize_i8); + deserialize_number!(deserialize_i16); + deserialize_number!(deserialize_i32); + deserialize_number!(deserialize_i64); + deserialize_number!(deserialize_i128); + deserialize_number!(deserialize_u8); + deserialize_number!(deserialize_u16); + deserialize_number!(deserialize_u32); + deserialize_number!(deserialize_u64); + deserialize_number!(deserialize_u128); + deserialize_number!(deserialize_f32); + deserialize_number!(deserialize_f64); + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + () => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + CStr(cstr) => visitor.visit_borrowed_str(cstr.to_str()?), + QDict(qdict) => visit_qdict_ref(qdict, visitor), + _ => Err(self.invalid_type(&"string or map")), + } + } + + #[inline] + fn deserialize_newtype_struct(self, name: &'static str, visitor: V) -> Result + where + V: Visitor<'de>, + { + let _ = name; + visitor.visit_newtype_struct(self) + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + bool(v) => visitor.visit_bool(v), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_char(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + CStr(cstr) => visitor.visit_borrowed_str(cstr.to_str()?), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + CStr(cstr) => visitor.visit_borrowed_str(cstr.to_str()?), + QList(qlist) => visit_qlist_ref(qlist, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_bytes(visitor) + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + () => visitor.visit_unit(), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + QList(qlist) => visit_qlist_ref(qlist, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + QDict(qdict) => visit_qdict_ref(qdict, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + match_qobject! { (self) => + QList(qlist) => visit_qlist_ref(qlist, visitor), + QDict(qdict) => visit_qdict_ref(qdict, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } +} + +pub fn from_qobject(value: QObject) -> Result +where + T: de::DeserializeOwned, +{ + T::deserialize(value) +} diff --git a/rust/util/src/qobject/error.rs b/rust/util/src/qobject/error.rs index 5212e65c4f7..2d7c180187a 100644 --- a/rust/util/src/qobject/error.rs +++ b/rust/util/src/qobject/error.rs @@ -6,7 +6,7 @@ str::Utf8Error, }; -use serde::ser; +use serde::{de, ser}; #[derive(Debug)] pub enum Error { @@ -23,6 +23,12 @@ fn custom(msg: T) -> Self { } } +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::Custom(msg.to_string()) + } +} + impl From for Error { fn from(_: NulError) -> Self { Error::NulEncountered diff --git a/rust/util/src/qobject/mod.rs b/rust/util/src/qobject/mod.rs index aec635a5ccc..1c18a491720 100644 --- a/rust/util/src/qobject/mod.rs +++ b/rust/util/src/qobject/mod.rs @@ -7,6 +7,7 @@ #![deny(clippy::unwrap_used)] mod deserialize; +mod deserializer; mod error; mod serialize; mod serializer; @@ -20,6 +21,7 @@ }; use common::assert_field_type; +pub use deserializer::from_qobject; pub use error::{Error, Result}; pub use serializer::to_qobject; -- 2.51.0