From bdb5fa95a61eeaea16ba20d8b0a7b88515d784c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 30 Jun 2024 16:54:48 +0300 Subject: [PATCH] glib: Add support for registering GTypes with name conflicts When explicitly allowed when defining the type, a GType name conflict wouldn't cause a panic anymore but instead a new name is found by appending a counter to the given name. This is useful if multiple definitions of the same type can end up in the same process. Also use this for `glib::BoxedAnyObject`, `gio::WriteOutputStream` and `gio::ReadInputStream`. Fixes https://github.com/gtk-rs/gtk-rs-core/issues/1430 --- gio/src/read_input_stream.rs | 1 + gio/src/write_output_stream.rs | 1 + glib-macros/src/boxed_derive.rs | 10 ++++- glib-macros/src/enum_derive.rs | 61 ++++++++++++++++++++++++-- glib-macros/src/flags_attribute.rs | 53 ++++++++++++++++++++-- glib-macros/src/lib.rs | 14 +++++- glib-macros/src/shared_boxed_derive.rs | 10 ++++- glib/src/boxed_any_object.rs | 1 + glib/src/subclass/boxed.rs | 48 +++++++++++++++++--- glib/src/subclass/interface.rs | 49 ++++++++++++++++++--- glib/src/subclass/shared.rs | 48 +++++++++++++++++--- glib/src/subclass/types.rs | 51 ++++++++++++++++++--- 12 files changed, 311 insertions(+), 36 deletions(-) diff --git a/gio/src/read_input_stream.rs b/gio/src/read_input_stream.rs index 4ec0d5a63a82..05fb5c020e07 100644 --- a/gio/src/read_input_stream.rs +++ b/gio/src/read_input_stream.rs @@ -25,6 +25,7 @@ mod imp { #[glib::object_subclass] impl ObjectSubclass for ReadInputStream { const NAME: &'static str = "ReadInputStream"; + const ALLOW_NAME_CONFLICT: bool = true; type Type = super::ReadInputStream; type ParentType = InputStream; type Interfaces = (crate::Seekable,); diff --git a/gio/src/write_output_stream.rs b/gio/src/write_output_stream.rs index 4a6c959c14d0..306b1f9bc924 100644 --- a/gio/src/write_output_stream.rs +++ b/gio/src/write_output_stream.rs @@ -27,6 +27,7 @@ mod imp { #[glib::object_subclass] impl ObjectSubclass for WriteOutputStream { const NAME: &'static str = "WriteOutputStream"; + const ALLOW_NAME_CONFLICT: bool = true; type Type = super::WriteOutputStream; type ParentType = OutputStream; type Interfaces = (crate::Seekable,); diff --git a/glib-macros/src/boxed_derive.rs b/glib-macros/src/boxed_derive.rs index f70a89ac1bea..ab51b68da0d9 100644 --- a/glib-macros/src/boxed_derive.rs +++ b/glib-macros/src/boxed_derive.rs @@ -97,11 +97,13 @@ pub fn impl_boxed(input: &syn::DeriveInput) -> syn::Result { .required() .value_required(); let mut nullable = NestedMetaItem::::new("nullable").value_optional(); + let mut allow_name_conflict = + NestedMetaItem::::new("allow_name_conflict").value_optional(); let found = parse_nested_meta_items( &input.attrs, "boxed_type", - &mut [&mut gtype_name, &mut nullable], + &mut [&mut gtype_name, &mut nullable, &mut allow_name_conflict], )?; if found.is_none() { @@ -113,6 +115,11 @@ pub fn impl_boxed(input: &syn::DeriveInput) -> syn::Result { let gtype_name = gtype_name.value.unwrap(); let nullable = nullable.found || nullable.value.map(|b| b.value()).unwrap_or(false); + let allow_name_conflict = allow_name_conflict.found + || allow_name_conflict + .value + .map(|b| b.value()) + .unwrap_or(false); let crate_ident = crate_ident_new(); @@ -130,6 +137,7 @@ pub fn impl_boxed(input: &syn::DeriveInput) -> syn::Result { Ok(quote! { impl #crate_ident::subclass::boxed::BoxedType for #name { const NAME: &'static ::core::primitive::str = #gtype_name; + const ALLOW_NAME_CONFLICT: bool = #allow_name_conflict; } impl #crate_ident::prelude::StaticType for #name { diff --git a/glib-macros/src/enum_derive.rs b/glib-macros/src/enum_derive.rs index e542e6164160..6cd04fe55d79 100644 --- a/glib-macros/src/enum_derive.rs +++ b/glib-macros/src/enum_derive.rs @@ -78,7 +78,13 @@ pub fn impl_enum(input: &syn::DeriveInput) -> syn::Result { let mut gtype_name = NestedMetaItem::::new("name") .required() .value_required(); - let found = parse_nested_meta_items(&input.attrs, "enum_type", &mut [&mut gtype_name])?; + let mut allow_name_conflict = + NestedMetaItem::::new("allow_name_conflict").value_optional(); + let found = parse_nested_meta_items( + &input.attrs, + "enum_type", + &mut [&mut gtype_name, &mut allow_name_conflict], + )?; if found.is_none() { return Err(syn::Error::new_spanned( @@ -87,6 +93,11 @@ pub fn impl_enum(input: &syn::DeriveInput) -> syn::Result { )); } let gtype_name = gtype_name.value.unwrap(); + let allow_name_conflict = allow_name_conflict.found + || allow_name_conflict + .value + .map(|b| b.value()) + .unwrap_or(false); let mut plugin_type = NestedMetaItem::::new("plugin_type").value_required(); let mut lazy_registration = @@ -105,10 +116,18 @@ pub fn impl_enum(input: &syn::DeriveInput) -> syn::Result { &crate_ident, name, gtype_name, + allow_name_conflict, g_enum_values, nb_enum_values, ), Some(_) => { + if allow_name_conflict { + return Err(syn::Error::new_spanned( + input, + "#[enum_dynamic] and #[enum_type(allow_name_conflict)] are not allowed together", + )); + } + let plugin_ty = plugin_type .value .map(|p| p.into_token_stream()) @@ -227,9 +246,45 @@ fn register_enum_as_static( crate_ident: &TokenStream, name: &syn::Ident, gtype_name: syn::LitStr, + allow_name_conflict: bool, g_enum_values: TokenStream, nb_enum_values: usize, ) -> TokenStream { + let type_name_snippet = if allow_name_conflict { + quote! { + unsafe { + let mut i = 0; + loop { + let type_name = ::std::ffi::CString::new(if i == 0 { + #gtype_name + } else { + format!("{}-{}", #gtype_name, i) + }) + .unwrap(); + if #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()) == #crate_ident::gobject_ffi::G_TYPE_INVALID + { + break type_name; + } + i += 1; + } + } + } + } else { + quote! { + unsafe { + let type_name = ::std::ffi::CString::new(#gtype_name).unwrap(); + assert_eq!( + #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()), + #crate_ident::gobject_ffi::G_TYPE_INVALID, + "Type {} has already been registered", + type_name.to_str().unwrap() + ); + + type_name + } + } + }; + // registers the enum on first use (lazy registration). quote! { impl #name { @@ -246,9 +301,9 @@ fn register_enum_as_static( value_nick: ::std::ptr::null(), }, ]; - let name = ::std::ffi::CString::new(#gtype_name).expect("CString::new failed"); + let type_name = #type_name_snippet; unsafe { - let type_ = #crate_ident::gobject_ffi::g_enum_register_static(name.as_ptr(), VALUES.as_ptr()); + let type_ = #crate_ident::gobject_ffi::g_enum_register_static(type_name.as_ptr(), VALUES.as_ptr()); let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_); assert!(type_.is_valid()); type_ diff --git a/glib-macros/src/flags_attribute.rs b/glib-macros/src/flags_attribute.rs index 9d40dc4bcd2d..22ec910efb8d 100644 --- a/glib-macros/src/flags_attribute.rs +++ b/glib-macros/src/flags_attribute.rs @@ -15,6 +15,7 @@ pub const WRONG_PLACE_MSG: &str = "#[glib::flags] only supports enums"; pub struct AttrInput { pub enum_name: syn::LitStr, + pub allow_name_conflict: bool, } struct FlagsDesc { variant: Variant, @@ -136,8 +137,8 @@ fn gen_default( }) } -pub fn impl_flags(attrs: AttrInput, input: &mut syn::ItemEnum) -> TokenStream { - let gtype_name = attrs.enum_name; +pub fn impl_flags(attr_meta: AttrInput, input: &mut syn::ItemEnum) -> TokenStream { + let gtype_name = attr_meta.enum_name; let syn::ItemEnum { attrs, @@ -167,10 +168,18 @@ pub fn impl_flags(attrs: AttrInput, input: &mut syn::ItemEnum) -> TokenStream { &crate_ident, name, gtype_name, + attr_meta.allow_name_conflict, g_flags_values, nb_flags_values, ), Ok(Some(_)) => { + if attr_meta.allow_name_conflict { + return syn::Error::new_spanned( + input, + "#[flags_dynamic] and #[glib::flags(allow_name_conflict)] are not allowed together", + ).to_compile_error(); + } + // remove attribute 'flags_dynamic' from the attribute list because it is not a real proc_macro_attribute attrs.retain(|attr| !attr.path().is_ident("flags_dynamic")); let plugin_ty = plugin_type @@ -281,9 +290,45 @@ fn register_flags_as_static( crate_ident: &TokenStream, name: &syn::Ident, gtype_name: syn::LitStr, + allow_name_conflict: bool, g_flags_values: TokenStream, nb_flags_values: usize, ) -> TokenStream { + let type_name_snippet = if allow_name_conflict { + quote! { + unsafe { + let mut i = 0; + loop { + let type_name = ::std::ffi::CString::new(if i == 0 { + #gtype_name + } else { + format!("{}-{}", #gtype_name, i) + }) + .unwrap(); + if #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()) == #crate_ident::gobject_ffi::G_TYPE_INVALID + { + break type_name; + } + i += 1; + } + } + } + } else { + quote! { + unsafe { + let type_name = ::std::ffi::CString::new(#gtype_name).unwrap(); + assert_eq!( + #crate_ident::gobject_ffi::g_type_from_name(type_name.as_ptr()), + #crate_ident::gobject_ffi::G_TYPE_INVALID, + "Type {} has already been registered", + type_name.to_str().unwrap() + ); + + type_name + } + } + }; + // registers the flags on first use (lazy registration). quote! { impl #name { @@ -301,9 +346,9 @@ fn register_flags_as_static( }, ]; - let name = ::std::ffi::CString::new(#gtype_name).expect("CString::new failed"); + let type_name = #type_name_snippet; unsafe { - let type_ = #crate_ident::gobject_ffi::g_flags_register_static(name.as_ptr(), VALUES.as_ptr()); + let type_ = #crate_ident::gobject_ffi::g_flags_register_static(type_name.as_ptr(), VALUES.as_ptr()); let type_: #crate_ident::Type = #crate_ident::translate::from_glib(type_); assert!(type_.is_valid()); type_ diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 915d318b71e7..69006bd9919c 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -785,13 +785,25 @@ pub fn flags(attr: TokenStream, item: TokenStream) -> TokenStream { let mut name = NestedMetaItem::::new("name") .required() .value_required(); + let mut allow_name_conflict_attr = + NestedMetaItem::::new("allow_name_conflict").value_optional(); - if let Err(e) = parse_nested_meta_items_from_stream(attr.into(), &mut [&mut name]) { + if let Err(e) = parse_nested_meta_items_from_stream( + attr.into(), + &mut [&mut name, &mut allow_name_conflict_attr], + ) { return e.to_compile_error().into(); } + let allow_name_conflict = allow_name_conflict_attr.found + || allow_name_conflict_attr + .value + .map(|b| b.value()) + .unwrap_or(false); + let attr_meta = AttrInput { enum_name: name.value.unwrap(), + allow_name_conflict, }; syn::parse::(item) diff --git a/glib-macros/src/shared_boxed_derive.rs b/glib-macros/src/shared_boxed_derive.rs index de007c0dd382..dc56813a1caf 100644 --- a/glib-macros/src/shared_boxed_derive.rs +++ b/glib-macros/src/shared_boxed_derive.rs @@ -106,11 +106,13 @@ pub fn impl_shared_boxed(input: &syn::DeriveInput) -> syn::Result::new("nullable").value_optional(); + let mut allow_name_conflict = + NestedMetaItem::::new("allow_name_conflict").value_optional(); let found = parse_nested_meta_items( &input.attrs, "shared_boxed_type", - &mut [&mut gtype_name, &mut nullable], + &mut [&mut gtype_name, &mut nullable, &mut allow_name_conflict], )?; if found.is_none() { @@ -121,6 +123,11 @@ pub fn impl_shared_boxed(input: &syn::DeriveInput) -> syn::Result syn::Result() -> crate::Type { unsafe { use std::ffi::CString; - let type_name = CString::new(T::NAME).unwrap(); - assert_eq!( - gobject_ffi::g_type_from_name(type_name.as_ptr()), - gobject_ffi::G_TYPE_INVALID, - "Type {} has already been registered", - type_name.to_str().unwrap() - ); + let type_name = if T::ALLOW_NAME_CONFLICT { + let mut i = 0; + loop { + let type_name = CString::new(if i == 0 { + T::NAME.to_string() + } else { + format!("{}-{}", T::NAME, i) + }) + .unwrap(); + if gobject_ffi::g_type_from_name(type_name.as_ptr()) == gobject_ffi::G_TYPE_INVALID + { + break type_name; + } + i += 1; + } + } else { + let type_name = CString::new(T::NAME).unwrap(); + assert_eq!( + gobject_ffi::g_type_from_name(type_name.as_ptr()), + gobject_ffi::G_TYPE_INVALID, + "Type {} has already been registered", + type_name.to_str().unwrap() + ); + + type_name + }; let type_ = crate::Type::from_glib(gobject_ffi::g_boxed_type_register_static( type_name.as_ptr(), diff --git a/glib/src/subclass/interface.rs b/glib/src/subclass/interface.rs index e93dd0932a42..5073e1649dc9 100644 --- a/glib/src/subclass/interface.rs +++ b/glib/src/subclass/interface.rs @@ -85,6 +85,24 @@ pub trait ObjectInterface: ObjectInterfaceType + Sized + 'static { /// This must be unique in the whole process. const NAME: &'static str; + // rustdoc-stripper-ignore-next + /// Allow name conflicts for this class. + /// + /// By default, trying to register a type with a name that was registered before will panic. If + /// this is set to `true` then a new name will be selected by appending a counter. + /// + /// This is useful for defining new types in Rust library crates that might be linked multiple + /// times in the same process. + /// + /// A consequence of setting this to `true` is that it's not guaranteed that + /// `glib::Type::from_name(Self::NAME).unwrap() == Self::type_()`. + /// + /// Note that this is not allowed for dynamic types. If a dynamic type is registered and a type + /// with that name exists already, it is assumed that they're the same. + /// + /// Optional. + const ALLOW_NAME_CONFLICT: bool = false; + /// Prerequisites for this interface. /// /// Any implementer of the interface must be a subclass of the prerequisites or implement them @@ -190,11 +208,32 @@ pub fn register_interface() -> Type { unsafe { use std::ffi::CString; - let type_name = CString::new(T::NAME).unwrap(); - assert_eq!( - gobject_ffi::g_type_from_name(type_name.as_ptr()), - gobject_ffi::G_TYPE_INVALID - ); + let type_name = if T::ALLOW_NAME_CONFLICT { + let mut i = 0; + loop { + let type_name = CString::new(if i == 0 { + T::NAME.to_string() + } else { + format!("{}-{}", T::NAME, i) + }) + .unwrap(); + if gobject_ffi::g_type_from_name(type_name.as_ptr()) == gobject_ffi::G_TYPE_INVALID + { + break type_name; + } + i += 1; + } + } else { + let type_name = CString::new(T::NAME).unwrap(); + assert_eq!( + gobject_ffi::g_type_from_name(type_name.as_ptr()), + gobject_ffi::G_TYPE_INVALID, + "Type {} has already been registered", + type_name.to_str().unwrap() + ); + + type_name + }; let type_ = gobject_ffi::g_type_register_static_simple( Type::INTERFACE.into_glib(), diff --git a/glib/src/subclass/shared.rs b/glib/src/subclass/shared.rs index e550169dbd93..4cf2fcc61a80 100644 --- a/glib/src/subclass/shared.rs +++ b/glib/src/subclass/shared.rs @@ -100,6 +100,21 @@ pub trait SharedType: StaticType + Clone + Sized + 'static { /// This must be unique in the whole process. const NAME: &'static str; + // rustdoc-stripper-ignore-next + /// Allow name conflicts for this boxed type. + /// + /// By default, trying to register a type with a name that was registered before will panic. If + /// this is set to `true` then a new name will be selected by appending a counter. + /// + /// This is useful for defining new types in Rust library crates that might be linked multiple + /// times in the same process. + /// + /// A consequence of setting this to `true` is that it's not guaranteed that + /// `glib::Type::from_name(Self::NAME).unwrap() == Self::static_type()`. + /// + /// Optional. + const ALLOW_NAME_CONFLICT: bool = false; + // rustdoc-stripper-ignore-next /// The inner refcounted type type RefCountedType: RefCounted; @@ -135,13 +150,32 @@ pub fn register_shared_type() -> crate::Type { ); } - let type_name = CString::new(T::NAME).unwrap(); - assert_eq!( - gobject_ffi::g_type_from_name(type_name.as_ptr()), - gobject_ffi::G_TYPE_INVALID, - "Type {} has already been registered", - type_name.to_str().unwrap() - ); + let type_name = if T::ALLOW_NAME_CONFLICT { + let mut i = 0; + loop { + let type_name = CString::new(if i == 0 { + T::NAME.to_string() + } else { + format!("{}-{}", T::NAME, i) + }) + .unwrap(); + if gobject_ffi::g_type_from_name(type_name.as_ptr()) == gobject_ffi::G_TYPE_INVALID + { + break type_name; + } + i += 1; + } + } else { + let type_name = CString::new(T::NAME).unwrap(); + assert_eq!( + gobject_ffi::g_type_from_name(type_name.as_ptr()), + gobject_ffi::G_TYPE_INVALID, + "Type {} has already been registered", + type_name.to_str().unwrap() + ); + + type_name + }; let type_ = crate::Type::from_glib(gobject_ffi::g_boxed_type_register_static( type_name.as_ptr(), diff --git a/glib/src/subclass/types.rs b/glib/src/subclass/types.rs index 138e4cf210db..f8c58101937c 100644 --- a/glib/src/subclass/types.rs +++ b/glib/src/subclass/types.rs @@ -616,6 +616,24 @@ pub trait ObjectSubclass: ObjectSubclassType + Sized + 'static { /// Optional. const ABSTRACT: bool = false; + // rustdoc-stripper-ignore-next + /// Allow name conflicts for this class. + /// + /// By default, trying to register a type with a name that was registered before will panic. If + /// this is set to `true` then a new name will be selected by appending a counter. + /// + /// This is useful for defining new types in Rust library crates that might be linked multiple + /// times in the same process. + /// + /// A consequence of setting this to `true` is that it's not guaranteed that + /// `glib::Type::from_name(Self::NAME).unwrap() == Self::type_()`. + /// + /// Note that this is not allowed for dynamic types. If a dynamic type is registered and a type + /// with that name exists already, it is assumed that they're the same. + /// + /// Optional. + const ALLOW_NAME_CONFLICT: bool = false; + // rustdoc-stripper-ignore-next /// Wrapper around this subclass defined with `wrapper!` type Type: ObjectType @@ -1013,13 +1031,32 @@ pub fn register_type() -> Type { unsafe { use std::ffi::CString; - let type_name = CString::new(T::NAME).unwrap(); - assert_eq!( - gobject_ffi::g_type_from_name(type_name.as_ptr()), - gobject_ffi::G_TYPE_INVALID, - "Type {} has already been registered", - type_name.to_str().unwrap() - ); + let type_name = if T::ALLOW_NAME_CONFLICT { + let mut i = 0; + loop { + let type_name = CString::new(if i == 0 { + T::NAME.to_string() + } else { + format!("{}-{}", T::NAME, i) + }) + .unwrap(); + if gobject_ffi::g_type_from_name(type_name.as_ptr()) == gobject_ffi::G_TYPE_INVALID + { + break type_name; + } + i += 1; + } + } else { + let type_name = CString::new(T::NAME).unwrap(); + assert_eq!( + gobject_ffi::g_type_from_name(type_name.as_ptr()), + gobject_ffi::G_TYPE_INVALID, + "Type {} has already been registered", + type_name.to_str().unwrap() + ); + + type_name + }; let type_ = Type::from_glib(gobject_ffi::g_type_register_static_simple( ::static_type().into_glib(),