From 20f0b8e98b4851bfd52bbb4edd4b602d08b9b817 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 24 Oct 2024 11:57:02 +0200 Subject: rust: macros: check that #[derive(Object)] requires #[repr(C)] Convert derive_object to the same pattern of first making a Result, and then doing .unwrap_or_else(Into::into) to support checking the validity of the input. Add is_c_repr to check that all QOM structs include a #[repr(C)] attribute. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/src/lib.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'rust/qemu-api-macros/src') diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 74a8bc7..160b283 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -32,18 +32,23 @@ fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), CompileError> { } } -#[proc_macro_derive(Object)] -pub fn derive_object(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = input.ident; +fn derive_object_or_error(input: DeriveInput) -> Result { + is_c_repr(&input, "#[derive(Object)]")?; - let expanded = quote! { + let name = &input.ident; + Ok(quote! { ::qemu_api::module_init! { MODULE_INIT_QOM => unsafe { ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::qom::ObjectImpl>::TYPE_INFO); } } - }; + }) +} + +#[proc_macro_derive(Object)] +pub fn derive_object(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = derive_object_or_error(input).unwrap_or_else(Into::into); TokenStream::from(expanded) } -- cgit v1.1 From e3ff5a17aa9fc2420197403e69876db48e390ee4 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 11 Dec 2024 11:48:44 +0100 Subject: rust: macros: check that the first field of a #[derive(Object)] struct is a ParentField Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/src/lib.rs | 46 ++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 19 deletions(-) (limited to 'rust/qemu-api-macros/src') diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 160b283..0f04cca 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -19,6 +19,27 @@ impl From for proc_macro2::TokenStream { } } +fn get_fields<'a>( + input: &'a DeriveInput, + msg: &str, +) -> Result<&'a Punctuated, CompileError> { + if let Data::Struct(s) = &input.data { + if let Fields::Named(fs) = &s.fields { + Ok(&fs.named) + } else { + Err(CompileError( + format!("Named fields required for {}", msg), + input.ident.span(), + )) + } + } else { + Err(CompileError( + format!("Struct required for {}", msg), + input.ident.span(), + )) + } +} + fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), CompileError> { let expected = parse_quote! { #[repr(C)] }; @@ -36,7 +57,12 @@ fn derive_object_or_error(input: DeriveInput) -> Result::ParentType>); + ::qemu_api::module_init! { MODULE_INIT_QOM => unsafe { ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::qom::ObjectImpl>::TYPE_INFO); @@ -53,30 +79,12 @@ pub fn derive_object(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } -fn get_fields(input: &DeriveInput) -> Result<&Punctuated, CompileError> { - if let Data::Struct(s) = &input.data { - if let Fields::Named(fs) = &s.fields { - Ok(&fs.named) - } else { - Err(CompileError( - "Cannot generate offsets for unnamed fields.".to_string(), - input.ident.span(), - )) - } - } else { - Err(CompileError( - "Cannot generate offsets for union or enum.".to_string(), - input.ident.span(), - )) - } -} - #[rustfmt::skip::macros(quote)] fn derive_offsets_or_error(input: DeriveInput) -> Result { is_c_repr(&input, "#[derive(offsets)]")?; let name = &input.ident; - let fields = get_fields(&input)?; + let fields = get_fields(&input, "#[derive(offsets)]")?; let field_names: Vec<&Ident> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect(); let field_types: Vec<&Type> = fields.iter().map(|f| &f.ty).collect(); let field_vis: Vec<&Visibility> = fields.iter().map(|f| &f.vis).collect(); -- cgit v1.1 From a3b620fff73ec762f2a77a077eb8389dddab4833 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 4 Dec 2024 08:57:27 +0100 Subject: rust: qemu-api-macros: extend error reporting facility to parse errors Generalize the CompileError tuple to an enum, that can be either an error message or a parse error from syn. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/src/lib.rs | 27 ++++++++++----------------- rust/qemu-api-macros/src/utils.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 rust/qemu-api-macros/src/utils.rs (limited to 'rust/qemu-api-macros/src') diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 0f04cca..539c48d 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -3,57 +3,50 @@ // SPDX-License-Identifier: GPL-2.0-or-later use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::{quote, quote_spanned}; +use quote::quote; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, Data, DeriveInput, Field, Fields, Ident, Type, Visibility, }; -struct CompileError(String, Span); - -impl From for proc_macro2::TokenStream { - fn from(err: CompileError) -> Self { - let CompileError(msg, span) = err; - quote_spanned! { span => compile_error!(#msg); } - } -} +mod utils; +use utils::MacroError; fn get_fields<'a>( input: &'a DeriveInput, msg: &str, -) -> Result<&'a Punctuated, CompileError> { +) -> Result<&'a Punctuated, MacroError> { if let Data::Struct(s) = &input.data { if let Fields::Named(fs) = &s.fields { Ok(&fs.named) } else { - Err(CompileError( + Err(MacroError::Message( format!("Named fields required for {}", msg), input.ident.span(), )) } } else { - Err(CompileError( + Err(MacroError::Message( format!("Struct required for {}", msg), input.ident.span(), )) } } -fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), CompileError> { +fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { let expected = parse_quote! { #[repr(C)] }; if input.attrs.iter().any(|attr| attr == &expected) { Ok(()) } else { - Err(CompileError( + Err(MacroError::Message( format!("#[repr(C)] required for {}", msg), input.ident.span(), )) } } -fn derive_object_or_error(input: DeriveInput) -> Result { +fn derive_object_or_error(input: DeriveInput) -> Result { is_c_repr(&input, "#[derive(Object)]")?; let name = &input.ident; @@ -80,7 +73,7 @@ pub fn derive_object(input: TokenStream) -> TokenStream { } #[rustfmt::skip::macros(quote)] -fn derive_offsets_or_error(input: DeriveInput) -> Result { +fn derive_offsets_or_error(input: DeriveInput) -> Result { is_c_repr(&input, "#[derive(offsets)]")?; let name = &input.ident; diff --git a/rust/qemu-api-macros/src/utils.rs b/rust/qemu-api-macros/src/utils.rs new file mode 100644 index 0000000..02c91ae --- /dev/null +++ b/rust/qemu-api-macros/src/utils.rs @@ -0,0 +1,26 @@ +// Procedural macro utilities. +// Author(s): Paolo Bonzini +// SPDX-License-Identifier: GPL-2.0-or-later + +use proc_macro2::Span; +use quote::quote_spanned; + +pub enum MacroError { + Message(String, Span), + ParseError(syn::Error), +} + +impl From for MacroError { + fn from(err: syn::Error) -> Self { + MacroError::ParseError(err) + } +} + +impl From for proc_macro2::TokenStream { + fn from(err: MacroError) -> Self { + match err { + MacroError::Message(msg, span) => quote_spanned! { span => compile_error!(#msg); }, + MacroError::ParseError(err) => err.into_compile_error(), + } + } +} -- cgit v1.1 From 809c703a60240125eec16ec134f60793134b4f61 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 4 Dec 2024 08:58:46 +0100 Subject: rust: qemu-api-macros: add automatic TryFrom/TryInto derivation This is going to be fairly common. Using a custom procedural macro provides better error messages and automatically finds the right type. Note that this is different from the same-named macro in the derive_more crate. That one provides conversion from e.g. tuples to enums with tuple variants, not from integers to enums. Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/qemu-api-macros/src/lib.rs | 74 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) (limited to 'rust/qemu-api-macros/src') diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 539c48d..7ec2182 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -5,8 +5,8 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ - parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, Data, DeriveInput, Field, - Fields, Ident, Type, Visibility, + parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, + DeriveInput, Field, Fields, Ident, Meta, Path, Token, Type, Variant, Visibility, }; mod utils; @@ -98,3 +98,73 @@ pub fn derive_offsets(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } + +#[allow(non_snake_case)] +fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { + let repr = input.attrs.iter().find(|attr| attr.path().is_ident("repr")); + if let Some(repr) = repr { + let nested = repr.parse_args_with(Punctuated::::parse_terminated)?; + for meta in nested { + match meta { + Meta::Path(path) if path.is_ident("u8") => return Ok(path), + Meta::Path(path) if path.is_ident("u16") => return Ok(path), + Meta::Path(path) if path.is_ident("u32") => return Ok(path), + Meta::Path(path) if path.is_ident("u64") => return Ok(path), + _ => {} + } + } + } + + Err(MacroError::Message( + format!("#[repr(u8/u16/u32/u64) required for {}", msg), + input.ident.span(), + )) +} + +fn get_variants(input: &DeriveInput) -> Result<&Punctuated, MacroError> { + if let Data::Enum(e) = &input.data { + if let Some(v) = e.variants.iter().find(|v| v.fields != Fields::Unit) { + return Err(MacroError::Message( + "Cannot derive TryInto for enum with non-unit variants.".to_string(), + v.fields.span(), + )); + } + Ok(&e.variants) + } else { + Err(MacroError::Message( + "Cannot derive TryInto for union or struct.".to_string(), + input.ident.span(), + )) + } +} + +#[rustfmt::skip::macros(quote)] +fn derive_tryinto_or_error(input: DeriveInput) -> Result { + let repr = get_repr_uN(&input, "#[derive(TryInto)]")?; + + let name = &input.ident; + let variants = get_variants(&input)?; + let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect(); + + Ok(quote! { + impl core::convert::TryFrom<#repr> for #name { + type Error = #repr; + + fn try_from(value: #repr) -> Result { + #(const #discriminants: #repr = #name::#discriminants as #repr;)*; + match value { + #(#discriminants => Ok(Self::#discriminants),)* + _ => Err(value), + } + } + } + }) +} + +#[proc_macro_derive(TryInto)] +pub fn derive_tryinto(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = derive_tryinto_or_error(input).unwrap_or_else(Into::into); + + TokenStream::from(expanded) +} -- cgit v1.1